pax_global_header00006660000000000000000000000064135145503220014512gustar00rootroot0000000000000052 comment=686a94e13e2da4c7309ef6c08dd965e56508bc36 apachetop-0.19.7/000077500000000000000000000000001351455032200135545ustar00rootroot00000000000000apachetop-0.19.7/.editorconfig000066400000000000000000000004251351455032200162320ustar00rootroot00000000000000root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 [*.{cc,c,h}] indent_style = tab indent_size = 4 tab_width = 4 [Makefile] indent_style = tab [configure.ac] indent_style = space indent_size = 4 [.travis.yml] indent_style = space indent_size = 2 apachetop-0.19.7/.gitignore000066400000000000000000000002231351455032200155410ustar00rootroot00000000000000*Makefile *.o src/.deps/ stamp-h1 *~ autom4te.cache/ src/apachetop config.h config.log config.status *.in configure aclocal.m4 config/ config.nice apachetop-0.19.7/AUTHORS000066400000000000000000000002171351455032200146240ustar00rootroot00000000000000Chris Elsworth Alex Kovalyov Nickolas Toursky Helmut K. C. Tessarek apachetop-0.19.7/COPYING000066400000000000000000000000141351455032200146020ustar00rootroot00000000000000SEE LICENSE apachetop-0.19.7/ChangeLog000066400000000000000000000275671351455032200153470ustar00rootroot00000000000000ApacheTop ChangeLog v0.19.7 (20th July, 2019) * attempt to find ncurses via pkg-config first (@Polynomial-C) * add .editorconfig for consistent coding style v0.18.4 (12th April, 2018) * fix for clang6 (@gonzalo) * add better build instructions to INSTALL * add option (-v) to show version * fix minor mem leak (8 bytes per apachetop invocation) v0.17.4 (25th April, 2017) * create config.nice when running ./configure * fix error: extra qualification on member (when using adns) * fix compiler warnings * fix a potential buffer overflow v0.15.6 (10th June, 2015) * allow other time/date formats in Apache log * use sys/param.h for MAXPATHLEN * fix deprecated auto tools macros * fix compiler warnings * use silent compile rules * updated man page * change version to 0.YY.M of new release v0.12.6 (27th October, 2005) * fixed security issue which described at CVE-2005-2660 v0.12.5 (27th November, 2004) 20041126 * Threading is no longer used. Everything now runs in a single thread. * change src/Makefile.am to install apachetop into bin, not sbin * add NetBSD compatibility hack for kqueue; their opaque datafield is an intptr_t (why?) 20040915 * add attron back into configure.ac - not sure when that got removed * change timeout delay between log checks to a constant 1/10th of a second * get rid of all threading code. It was never really necessary; only ever implemented because the main-loop delay was too long. Now it runs at least 10 times a second. 20040725 * incorporate gcc 3.4 building compatibility patch from Pascal Terjan v0.12 (21st May, 2004) * Add FAM support; event notification for Linux etc. * Add adns support; ApacheTop can now resolve your IPs into Hosts. Please give me feedback on this; it's not very widely tested. You'll need to use the cmdline switch -r to enable it. * Add return code breakdown for each item. Press 'n' while running. * Header now converts to GB/MB/KB/Bytes where appropriate. * TAKE NOTE! Commandline option changes: -r for refresh delay has changed to -d -r has been re-assigned to enable resolving of hosts/ips. 20040508 * finish up adns resolving; now host and/or ip are displayed as best as possible; if one is not available it is displayed as ... while being resolved. * TAKE NOTE! commandline option -r has changed to -d -r has been reassigned to enable resolving of hosts/ips 20040505 * use kqueue and fam facilities for passing opaque user data back when an event occurs, allowing the removal of the ift struct (which sucked) 20040504 * add runtime option key 'n' to toggle number columns between hits/bytes and return code breakdowns for each item. * expand sorting options so it's possible to sort by return codes as well as hits/bytes. Pressing 's' brings up a different menu if you're viewing return code breakdown. 20040416 * add return code breakdown for each item in the top-list 20040308 * improve header to display GB/MB/KB/Bytes in all appropriate fields 20040303 * add adns support to resolve IPs into Hosts; if HostnameLookups is Off in your httpd.conf, ApacheTop will now look up IPs for you. Created --with-adns, like pcre/fam. 20040301 * remove --enable-pcre; have configure always check for it and just warn if it can't be found. Specify --with-pcre to point it at the right place. * call realpath() on supplied filenames for sanity * add File Alteration Module (FAM) monitoring code (for Linux/IRIX mostly) Use --with-fam to point it at the right place, like pcre. v0.11 (25th February, 2004) 20040224 * acknowledge & as starting a query string as well as ? 20040219 * clean up configure.ac, remove unused function checks * add --with-libraries and --with-includes to ./configure to provide hints about where to look other than standard places 20040218 * add --enable-pcre and --with-pcre= to ./configure; providing these enables regular expression filtering * add new filters submenu (press f) * add --with-logfile= to ./configure; now you can override the default log position without editing the source 20040216 * inline hashing functions for greater efficiency * replace localtime & strftime calls with a bit of maths in display_header() 20040204 * add largefile checks into configure.ac 20040119 * change nanosleep to usleep; I think this makes more sense.. Solaris 2.6 doesn't have nanosleep, and it seems overkill * change configure.ac check for wattr_on to attron (fix Solaris compile issue) v0.10 (14th January, 2004) 20040104 * remove old useless configure.ac stuff * fix crash bug when ApacheTop has nothing to display v0.9 (22nd December, 2003) 20031222 * don't call endwin() in a signal handler; set a flag and get the main loop to do it. Fixes intermittent segfault when Ctrl-C'ing to exit. * change instances of mvprintw() to mvaddstr() where printf features were unused anyway; should give curses an easier time. * add runtime help display; press s or ? 20031221 * rework runtime options to make use of "submenus" * add facility for removing detailed-display sections (remove Referrers from a URL detailed display etc); press t during runtime for submenu * move sort runtime keys into their own submenu; press s for this 20031218 * revamp pthreads detection in configure.ac. It works now, I swear. 20031212 * code cleanups in display.cc * new display mode; press Right-Arrow to show statistics specific to the currently highlighted item. If you are highlighting a URL, host and referrer statistics for that URL will be shown. Press Left-Arrow to exit this display mode. v0.8.1 (28th November, 2003) 20031128 * fix for running ApacheTop with no parameters v0.8 (18th November, 2003) 20031115 * we now reopen an input file if the inode changes; this catches deletes and renames. ApacheTop will wait for the file to be recreated and reopen. 20031113 * remove mod_log_spread code; I'm not happy with including this since I have no idea how it works, no idea how the new filecode breaks it, and I haven't had chance to test it. This will be re-introduced when I can test it * remove option -t; all logs are assumed to be common or combined format * adapt internally to facilitate reading from more than one log at once 20031102 * add more kqueue flags to watch for file deletion/renaming 20031027 * fiddle with configure.ac so it doesn't require autoconf 2.57 20031014 * added check for stdarg.h to configure.ac; fixes Solaris build issue * fix configure.ac check for pthread library v0.7 (14th October, 2003) 20031008 * add thread for monitoring keypresses; if pthreads can be used, this will make ApacheTop feel more responsive. Fall back on old behaviour if not. 20031006 * simplify log-fetching routine; no need to lseek() around the file; we just wait (or kqueue) then attempt to read(). 20031005 * wrap header includes in #if/#endif; we should now build on ancient standards-breaking systems which don't have some headers. * efficiency tweaks to hashing functions and hash class * use the return value of ohtbl->insert where possible; saves a call to lookup and thus another hash in some cases, which cuts down on CPU time. v0.6 (5th October, 2003) 20031004 * change from select() to nanosleep() for sleeping. Not only is this a more suitable function, it also stops ApacheTop on Linux eating all the CPU, since select() was zero'ing a structure after it's first call. doh. * start distributing with autoconf. Disregard previous build instructions; you now do the more conventional cd apachetop-X.X/ && ./configure && make * add kqueue() support for checking the logfile for updates; autoconf enables this automatically if your architecture supports it. 20031003 * drop qsort() in favour of shellsort() in display.cc; this has the advantage of knowing the data it's sorting, thus doesn't have to call a comparison sub-function millions of times. This should lower ApacheTop's CPU usage a lot. * add mod_log_spread support via a patch from Theo Schlossnagle; I haven't been able to test this yet. v0.5 (2nd October, 2003) 20031002 * make the timed circle store slightly more information than requested, so that we're guaranteed to have the requested number of seconds. 20031001 * add option -r to specify refresh delay interval in seconds. * add runtime key 'p' to pause/unpause the updating display. Stats are still collected, but the screen is temporarily frozen. 20030930 * experimental try-to-stop-it-crashing changes; a few cases of possible memory trashing were fixed in hash and map classes. * fill in dummy "Unknown" value for referrer if a common log is used. (fixes crash if user switches to REFERRERs when there aren't any) * change default refresh delay to 5 seconds. v0.4 (29th September, 2003) 20030929 * add a new display mode; REFERRERs. Press 'd' to cycle through URLs, IPs, and now REFERRERs. * add option -p to preserve http:// (or whatever protocol) at start of referrer string. By default it is cut out to save space onscreen. 20030928 * remove -lreadline and associated code; hence dropping support for altering delay frequency during runtime. This improves portability, notably to Solaris. Intending to replace readline with own code. * -L changed to -l; no real reason to use uppercase, not sure why I did. 20030927 * add -s option to keep a given number of URL path segments only. For example, -s 2 converts "/images/media/small/x.jpg" into "/images/media/" This is rather experimental, so I'd appreciate feedback. * remove floating points from table information when the numbers get big enough; so we can display bigger numbers in the same table space. * internally record referrers from log if they're available. * add a visual marker (* just to left of URL/IP), movable by Up/Dn to stats table; will be used to provide more information on the selected item. This doesn't yet do anything further, however. v0.3 (26th September, 2003) 20030926 * add framework for reading more logformats; combined & atop (not done yet) * add -L option to lowercase all URLs for display (means /FOO and /foo are considered the same and accumulate the same hits/bytes counters) * add -q option to keep querystrings on URLs (default is to remove) * rejigged -h and -t options to be -H and -T; logtype now uses -t 20030925 * simplify circle functions by passing structs around, not lots of individual ints; cuts down on memory copying too (= faster). * display bytecount in header in MB when appropriate. * adapt to resizes of the term; display more results if we can, and use more of the term's width if there's an excess. 20030924 * log.[cc|h] added; Log parsing class. Obsoletes logsplit() in apachetop.cc. Currently only has common logformat parser, but is easily extendable to do combined (and planning on a custom format) * don't display NaN when there's no stats; display zeroes. v0.2 (24th September, 2003) 20030923 * timed_circle.cc operational improvements to return more accurate information about what's really going on with idle-ish servers. * only allow one of -t and -h args to be specified. 20030922 * timed_circle.cc fixes for potentially overrunning arrays, with the help of Purify on Solaris. This should fix the segmentation faults a few people have been randomly seeing. v0.1 (22nd September, 2003) 20030922 * README (some brief docs) and CHANGES added :) * display restructured a little; better use made of floating points 20030921 * circle.[cc|h] moved to hits_circle.*; circle.h added as a virtual class. In English, this means you can choose which circle mode to use at startup time; one limited by time or one by hits. 20030920 * timed_circle.cc added; provide detailed statistics for server requests in the last $x seconds. The alternative is circle.cc, which similarly remembers the last $x requests, regardless of age. v0.0 (19th September, 2003) 20030919 * initial freshmeat release apachetop-0.19.7/INSTALL000066400000000000000000000057261351455032200146170ustar00rootroot00000000000000+----------------------------------------------------------------------+ | ApacheTop INSTALL Instructions | +----------------------------------------------------------------------+ ApacheTop is now distributed with autoconf files, a nice easy way to build distributions whatever platform you're on. 1) Building ApacheTop from a cloned repository 2) Building ApacheTop from a tarball 3) custom configure options 4) If it fails to build +----------------------------------------------------------------------+ | 1. Building ApacheTop from a cloned repository | +----------------------------------------------------------------------+ git clone https://github.com/tessus/apachetop.git cd apachetop ./autogen.sh (autotools required) ./configure (see section 3 for details) make make install (as root or with sudo) +----------------------------------------------------------------------+ | 2. Building ApacheTop from a tarball | +----------------------------------------------------------------------+ Download the latest tarball from: https://github.com/tessus/apachetop/releases/latest tar -xzf apachetop-X.Y.Z.tar.gz cd apachetop-X.Y.Z ./configure (see section 3 for details) make make install (as root or with sudo) +----------------------------------------------------------------------+ | 3. custom configure options | +----------------------------------------------------------------------+ There's a few custom ./configure options and overrides: --with-logfile= (added in v0.11) You may specify the location of the default logfile to open. This overrides the #define in apachetop.h. Of course, you can just use -f on the apachetop commandline, but if you'll only ever use one log, you can give it to configure and never worry about it again. --with-pcre= (added in v0.11) Specifies where to find the pcre installation in the event it's not in your standard path. configure should look for /include/pcre.h and /lib/libpcre.* --with-fam= (added in v0.12) Specifies where to find the FAM installation. The path you give should contain include/fam.h and lib/libfam.* kqueue will be preferred to fam if both are found on the system. --with-pcre= (added in v0.12) Specifies where to find an adns installation. +----------------------------------------------------------------------+ | 4. If it fails to build | +----------------------------------------------------------------------+ Make sure you have the -dev sources for readline and (n)curses. These contain files required to build binaries that use these libraries; for example on Debian you'll need the libreadline4-dev package installed. apachetop-0.19.7/LICENSE000066400000000000000000000015511351455032200145630ustar00rootroot00000000000000License: BSD Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The names of the authors may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. apachetop-0.19.7/Makefile.am000066400000000000000000000004071351455032200156110ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in AUTOMAKE_OPTIONS = foreign SUBDIRS = man src EXTRA_DIST = LICENSE INSTALL README ChangeLog MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure configuration.h.in \ stamp-h.in apachetop-0.19.7/NEWS000066400000000000000000000000161351455032200142500ustar00rootroot00000000000000see ChangeLog apachetop-0.19.7/README000066400000000000000000000163611351455032200144430ustar00rootroot00000000000000ApacheTop Readme ApacheTop watches a logfile generated by Apache (in standard common or combined logformat, and generates human-parsable output in realtime. See the INSTALL file for ./configure options (there's a few newly added since v0.11) Several commandline options dictate some of its' behaviour: -f logfile Select which file to watch. Specify this option multiple times to watch multiple files -H hits | -T time These options are mutually exclusive. Specify only one, if any at all. They work as follows. ApacheTop maintains a table of information internally containing all the relevant information about the hits it's seen. This table can only be a finite size, so you need to decide how big it's going to be. You have two options. You can either: Use -H to say "remember hits" or Use -T to say "remember all hits in seconds" The default (at the moment) is to remember hits for 30 seconds. Setting this too large (whichever option you choose) will cause ApacheTop to use more memory and more CPU time. My experimentation finds that remembering no more than around 5000 requests works well. -q Instructs ApacheTop to keep the querystrings, not remove them -l Instructs ApacheTop to lowercase all URLs, thus /FOO and /foo are treated as the same and accumulate the same statistics. -r Enable resolving of hosts/ips (you need adns!) -s segments Instructs ApacheTop to only keep the first parts of the path. Trailing slashes are kept if present. Statistics are then merged for each truncated url. This is easiest to demonstrate with examples: -s 2 would produce the following: /media/x.jpg -> /media/x.jpg /media/images/x.jpg -> /media/images/ /media/images/small/x.jpg -> /media/images/ /media/images/big/x.jpg -> /media/images/ Stats for the last three URLs would be merged in this case. -p Instructs ApacheTop to keep the protocol (http:// usually) at the front of its' referrer strings. Normal behaviour is to remove them to give more room to more useful information. -d secs Set default refresh delay, in seconds. Once it's running, you'll see a display like this: last hit: 09:17:07 atop runtime: 0 days, 00:58:20 09:17:08 All: 638924 reqs ( 182.65/sec) 3433539K ( 981.6K/sec) ( 5.4K/req) 2xx: 455415 (71.3%) 3xx: 175745 (27.5%) 4xx: 7746 ( 1.2%) 5xx: 10 ( 0.0%) R ( 30s): 5195 reqs ( 173.17/sec) 25405K ( 846.8K/sec) ( 4.9K/req) 2xx: 3447 (66.4%) 3xx: 1715 (33.0%) 4xx: 33 ( 0.6%) 5xx: 0 ( 0.0%) REQS REQ/S KB KB/S URL 103 3.4 2983 99.4 / 56 1.9 239 8.0 /tickerdata/story2.dat 47 1.6 104 3.6 /home/today/patina.js 44 1.5 82 2.8 /home/styles/home_d0e2ee.css The top line displays the time the last hit was seen, how long it's been running, and the current time. The next two lines display information about every single hit ApacheTop has processed in this incarnation. Firstly you see how many hits the data is representing. After that, the average number of hits/second since starting. Following that, the total number of KB witnessed; then the average KB/sec. Finally you see the average KB per request. The next line shows a breakdown of return codes; in this particular example you can see that 71.3% of the hits returned a 2xx code. 27.5% were 3xx, and so on. You also have the actual number of hits in each group. The two lines below this are where the commandline options -h and -t come in. The data in these lines reads the same as the two above them, but this data is only for the hits remembered in ApacheTop's internal table (remember that?). You can see how many seconds of data this represents in the R ( 30s) at the beginning of the line. This is for 30 seconds. These two lines of information are good for a "what is my server doing *right now*?" scenario, while the two above them are good for a picture over the course of a few minutes or hours. Underneath this header, you'll see a list of URLs along with their relevant number of requests, requests per second, kb, and kb per second. This list is generated from the internal table ApacheTop maintains. Thus, in this example, the list is being generated from the last 30 seconds of data. You can see the root page has been requested 103 times in the last 30 seconds, resulting in about 3.4 hits per second. Additionally, those 103 requests have resulted in 2983K of traffic, at an average of 99.4K/second. You can see the individual number of return codes a given item has generated by pressing 'n'. This alternates the numbers columns between hits/bytes and return codes for each item. You may sort this list by any of the first four columns; first press 's' to enter the 'sort submenu', and then one of the following: r Sort by REQUESTS R Sort by REQUESTS/SECOND b Sort by BYTES B Sort by BYTES/SECOND If you are viewing return code breakdown, then you'll see the following: 2 2xx 3 3xx 4 4xx 5 5xx Thus you can see where all your Page Not Founds are coming from and so on. Each sort order is individually maintained, so you can sort by 3xx, and Bytes, for example, then freely switch between number modes (using 'n') without losing either setting. Additionally, you can press d during runtime to switch the list of displayed items between URLs, IPs, and REFERRERs. URLs is the default, and simply groups together hits on your site and provides collated stats for each one. IPs, similarly, groups hits from each IP and shows you stats for it. So you can see how much bandwidth is being used by any given IP. REFERRERs is handy if you want to see where your traffic is coming from. The stats here reflect how many pages/kbytes have been served as a result of a particular referrer. To hold the current screen at any time, press p - statistics will still be generated in the background, but whatever is displayed at the current time is kept onscreen until you press p again. The asterisk beside the URL/IP/Referrer entry in the table can be used to restrict the display to any entry you're interested in. Use Up/Down arrow keys to move the asterisk to an entry you're interested in (you can use 'p' to freeze the display to give you more time to do so) and then press Right arrow to enter the display specific for that item. If the item you expanded is a URL, then IPs and Referrers specific to that URL will be shown; ie, IPs (or hosts) which are visiting that URL, and Referrers which are referring people to that URL. If the item is an IP/Host, then URLs that IP/Host is visiting will be displayed, along with the referrers that IP is coming from. If the item is a Referrer, then URLs and IPs will be shown which have that Referrer. You may turn off any of these subcolumns; press 't' to enter the toggle submenu, then: u Toggles URL subdisplay r Toggles REFERRER subdisplay h Toggles HOSTS subdisplay Thus you can only display HOSTS that are visiting a given URL, etc. Use Left arrow to return to the previous display. Bug reports and patches are very welcome. Please send any comments on. (if anyone fancies rewriting this README so its a bit more readable..) Chris Elsworth apachetop-0.19.7/autogen.sh000077500000000000000000000000241351455032200155510ustar00rootroot00000000000000autoreconf -i -v -f apachetop-0.19.7/configure.ac000066400000000000000000000074561351455032200160560ustar00rootroot00000000000000# Process this file with autoconf to produce a configure script. AC_INIT([apachetop],[0.19.7],[https://github.com/tessus/apachetop/issues]) AC_DEFUN([AC_CONFIG_NICE], [ test -f $1 && mv $1 $1.old rm -f $1.old cat >$1<> $1 fi done for arg in [$]0 "[$]@"; do echo "'[$]arg' \\" >> $1 done echo '"[$]@"' >> $1 chmod +x $1 ]) AC_CONFIG_NICE(config.nice) AM_SILENT_RULES([yes]) AC_CONFIG_SRCDIR(src/apachetop.cc) AC_CONFIG_AUX_DIR(config) AM_CONFIG_HEADER(config.h) AM_INIT_AUTOMAKE PKG_PROG_PKG_CONFIG # Add non-standard directories to the include path AC_ARG_WITH(libraries, [ --with-libraries= additional place to look for libraries], [LDFLAGS="$LDFLAGS -L $withval"], , ) # Add non-standard includes to the include path AC_ARG_WITH(includes, [ --with-includes= additional place to look for header files], [CPPFLAGS="$CPPFLAGS -I $withval"], , ) # Checks for programs. AC_PROG_CXX AC_LANG([C++]) # Checks for header files. AC_HEADER_STDC AC_CHECK_HEADERS([arpa/inet.h netdb.h netinet/in.h \ string.h strings.h sys/socket.h time.h sys/time.h \ limits.h sys/param.h]) # Checks for typedefs, structures, and compiler characteristics. #AC_HEADER_STDBOOL # not in 2.53? AC_C_CONST AC_HEADER_TIME AC_STRUCT_TM AC_TYPE_OFF_T AC_TYPE_SIZE_T AC_SYS_LARGEFILE # Checks for library functions. AC_FUNC_MALLOC AC_FUNC_VPRINTF AC_CHECK_FUNCS([inet_aton memset strchr strdup kqueue strerror strstr]) # pcre {{{ AC_ARG_WITH(pcre, [ --with-pcre= prefix of pcre installation (eg /usr/local)], [ CPPFLAGS="$CPPFLAGS -I $withval/include" LDFLAGS="$LDFLAGS -L $withval/lib" ] ) AC_CHECK_HEADERS(pcre.h, AC_SEARCH_LIBS([pcre_compile], [pcre]) , AC_MSG_WARN([*** pcre.h not found -- consider using --with-pcre]) ) # }}} # fam {{{ AC_ARG_WITH(fam, [ --with-fam= prefix of fam installation (eg /usr/local)], [ CPPFLAGS="$CPPFLAGS -I $withval/include" LDFLAGS="$LDFLAGS -L $withval/lib" ] ) AC_CHECK_HEADERS(fam.h, AC_SEARCH_LIBS([FAMOpen], [fam]) , AC_MSG_WARN([*** fam.h not found -- consider using --with-fam]) ) # }}} # adns {{{ AC_ARG_WITH(adns, [ --with-adns= prefix of adns installation (eg /usr/local)], [ CPPFLAGS="$CPPFLAGS -I $withval/include" LDFLAGS="$LDFLAGS -L $withval/lib" ] ) AC_CHECK_HEADERS(adns.h, AC_SEARCH_LIBS([adns_submit], [adns]) , AC_MSG_WARN([*** adns.h not found -- consider using --with-adns]) ) # }}} # --with-logfile {{{ AC_ARG_WITH(logfile, [ --with-logfile= location of default logfile], [AC_DEFINE_UNQUOTED(DEFAULT_LOGFILE, "$withval", [Optionally override the DEFAULT_LOGFILE in apachetop.h])], , ) # }}} AC_SEARCH_LIBS([socket], [socket]) AC_SEARCH_LIBS([inet_addr], [nsl]) NCURSES_FOUND=no PKG_CHECK_MODULES(NCURSES, ncurses, [ LIBS="$LIBS $NCURSES_LIBS" NCURSES_FOUND=yes ]) AS_IF([test "x$NCURSES_FOUND" != "xyes"], [ AC_SEARCH_LIBS([attron], [ncurses]) AC_SEARCH_LIBS([tgetstr], [termcap]) AC_SEARCH_LIBS([mvprintw], [curses ncurses] , [] , [ AC_MSG_ERROR([No useful curses library found!]) ] ) ]) AC_SEARCH_LIBS([readline], [readline], [ AC_DEFINE(HAVE_READLINE,1,[Define if you have readline library]) AC_SUBST(HAVE_READLINE) ] , [ AC_MSG_ERROR(readline library not found) ] ) # everything is in CPPFLAGS up to this point, now we move to CXXFLAGS # this is mostly done for pcre test, AC_CHECK_HEADERS wants to use CPP #CXXFLAGS="$CXXFLAGS $CPPFLAGS" AC_SUBST(CXXFLAGS) AC_CONFIG_FILES([Makefile man/Makefile src/Makefile]) AC_OUTPUT apachetop-0.19.7/man/000077500000000000000000000000001351455032200143275ustar00rootroot00000000000000apachetop-0.19.7/man/Makefile.am000066400000000000000000000002211351455032200163560ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in. man_MANS = apachetop.1 EXTRA_DIST = $(man_MANS) MAINTAINERCLEANFILES = Makefile.in apachetop-0.19.7/man/apachetop.1000066400000000000000000000040201351455032200163510ustar00rootroot00000000000000.TH apachetop 1 "June, 2015" "version 0.15.6" "USER COMMANDS" .SH NAME apachetop \- display real-time web server statistics .SH SYNOPSIS .B apachetop [-f filename] [-H hits | -T time] [-q] [-l] [-s segments] [-p] [-r secs] .SH DESCRIPTION ApacheTop watches a logfile generated by Apache (in standard common or combined logformat, and generates human-parsable output in realtime. .OPTIONS .TP -f logfile Select which file to watch. Specify this option multiple times to watch multiple files. .TP -H hits | -T time These options are mutually exclusive. Specify only one, if any at all. They work as follows. ApacheTop maintains a table of information internally containing all the relevant information about the hits it's seen. This table can only be a finite size, so you need to decide how big it's going to be. You have two options. You can either: Use -H to say "remember hits" or Use -T to say "remember all hits in seconds" The default (at the moment) is to remember hits for 30 seconds. Setting this too large (whichever option you choose) will cause ApacheTop to use more memory and more CPU time. My experimentation finds that remembering no more than around 5000 requests works well. .TP -q Instructs ApacheTop to keep the querystrings, not remove them. .TP -l Instructs ApacheTop to lowercase all URLs, thus /FOO and /foo are treated as the same and accumulate the same statistics. .TP -s segments Instructs ApacheTop to only keep the first parts of the path. Trailing slashes are kept if present. Statistics are then merged for each truncated url. .TP -p Instructs ApacheTop to keep the protocol (http:// usually) at the front of its' referrer strings. Normal behaviour is to remove them to give more room to more useful information. .TP -r secs Set default refresh delay, in seconds. .SH EXAMPLES .TP apachetop -f /var/logs/httpd/access.log .SH AUTHOR Chris Elsworth .SH REPORTING BUGS Report bugs at: https://github.com/tessus/apachetop/issues .SH SEE ALSO https://www.cae.me.uk/projects/apachetop apachetop-0.19.7/src/000077500000000000000000000000001351455032200143435ustar00rootroot00000000000000apachetop-0.19.7/src/Makefile.am000066400000000000000000000007541351455032200164050ustar00rootroot00000000000000## Process this file with automake to produce Makefile.in. bin_PROGRAMS = apachetop apachetop_SOURCES = apachetop.cc log.cc inlines.cc ohtbl.cc map.cc queue.cc \ display.cc hits_circle.cc timed_circle.cc filters.cc \ resolver.cc noinst_HEADERS = apachetop.h log.h ohtbl.h map.h queue.h \ display.h hits_circle.h circle.h timed_circle.h filters.h \ resolver.h pcre_cpp_wrapper.h MAINTAINERCLEANFILES = Makefile.in apachetop-0.19.7/src/apachetop.cc000066400000000000000000000605661351455032200166330ustar00rootroot00000000000000/* APACHETOP ** ** */ #include "apachetop.h" /* die and report why */ #define DIE(msg) fprintf(stderr, "%s: %s\n", msg, strerror(errno)); catchsig(1); /* die with no strerror */ #define DIE_N(msg) fprintf(stderr, "%s\n", msg); catchsig(1); #if HAVE_ADNS_H /* resolver master state */ adns_state adns; #endif /* global stats */ struct gstat gstats; time_t now; struct itemlist *items = NULL; map *last_display_map; Circle *c; Timed_Circle *tc; Hits_Circle *hc; map *um, /* urlmap */ *im, /* ipmap */ *hm, /* hostmap */ *rm, /* referrermap */ *fm; /* filemap */ struct config cf; /* inputs */ struct input *in; WINDOW *win; #if (POLLING_METHOD == USING_KQUEUE) int kq; #elif (POLLING_METHOD == USING_FAM) /* master fd for talking to FAM */ FAMConnection fc; #endif int main(int argc, char *argv[]) { int fd, buflen, ch, x; char buf[32768], *bufcp, *nextline; time_t last_display = 0; struct logbits lb; struct input *curfile; bool seen_h = false, seen_t = false; extern char *optarg; #if (POLLING_METHOD == USING_KQUEUE) /* we have and are using kqueue */ struct timespec stv; struct kevent *ev, *evptr; #elif (POLLING_METHOD == USING_FAM) /* we have and are using FAM */ int fam_fd; FAMEvent fe; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; fd_set readfds; #else /* stat() then */ struct stat sb; struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; #endif LogParser *p; CommonLogParser *cmmp; //AtopLogParser *atpp; /* some init stuff will need to know the time */ now = time(NULL); /* set up initial configuration {{{ */ memset(&cf, 0, sizeof(cf)); cf.debug = true; cf.current_display_size = 0; cf.input_count = 0; cf.circle_size = DEFAULT_CIRCLE_SIZE; cf.circle_mode = DEFAULT_CIRCLE_MODE; cf.sort = DEFAULT_SORT; cf.retcodes_sort = DEFAULT_RETCODES_SORT; cf.refresh_delay = DEFAULT_REFRESH_DELAY; cf.display_mode = DEFAULT_DISPLAY_MODE; cf.numbers_mode = DEFAULT_NUMBERS_MODE; cf.detail_display_urls = true; cf.detail_display_hosts = true; cf.detail_display_refs = true; cf.display_paused = false; cf.keep_querystring = false; cf.lowercase_urls = false; cf.keep_segments = 0; cf.preserve_ref_protocol = 0; cf.do_immed_display = false; cf.do_resolving = false; cf.urlfilter = new Filter(); cf.reffilter = new Filter(); cf.hostfilter = new Filter(); /* }}} */ /* support MAX_INPUT_FILES input files */ in = (struct input *)calloc(MAX_INPUT_FILES, sizeof(struct input)); #if (POLLING_METHOD == USING_KQUEUE) /* then create kqueue {{{ */ if ((kq = kqueue()) < 0) { DIE("cannot create kqueue"); exit(1); } /* space in ev for all inputs */ ev = (struct kevent *)calloc(MAX_INPUT_FILES, sizeof(struct kevent)); /* }}} */ #elif (POLLING_METHOD == USING_FAM) /* open FAM connection */ if (FAMOpen(&fc)) { DIE("cannot connect to fam"); exit(1); } fam_fd = FAMCONNECTION_GETFD(&fc); #endif #if HAVE_ADNS_H /* open adns connection */ adns_init(&adns, adns_if_noerrprint, 0); #endif /* process commandline {{{ */ while ((ch = getopt(argc, argv, "f:H:T:hvqlrs:pd:")) != -1) { switch(ch) { case 'f': if (new_file(optarg, SEEK_TO_END) == -1) { fprintf(stderr, "opening %s: %s\n", optarg, strerror(errno)); sleep(2); } else cf.input_count++; break; case 'T': x = atoi(optarg); seen_t = true; if (x > 0) { cf.circle_mode = TIMED_CIRCLE; cf.circle_size = x; } break; case 'H': x = atoi(optarg); seen_h = true; if (x > 0) { cf.circle_mode = HITS_CIRCLE; cf.circle_size = x; } break; case 'q': cf.keep_querystring = true; break; case 'l': cf.lowercase_urls = true; break; case 'r': cf.do_resolving = true; break; case 's': x = atoi(optarg); if (x > 0) cf.keep_segments = x; break; case 'p': cf.preserve_ref_protocol = 1; break; case 'd': x = atoi(optarg); if (x > 0) cf.refresh_delay = x; break; case 'v': version(); exit(0); break; case 'h': case '?': usage(); exit(1); break; } } /* }}} */ /* if no files have been specified, we'll use DEFAULT_LOGFILE */ if (cf.input_count == 0) { if (new_file(DEFAULT_LOGFILE, SEEK_TO_END) != -1) cf.input_count++; /* if it's still zero, fail */ if (cf.input_count == 0) { fprintf(stderr, "opening %s: %s\n", DEFAULT_LOGFILE, strerror(errno)); DIE_N("No input files could be opened"); exit(1); } } if (seen_t && seen_h) { DIE_N("-T and -H are mutually exclusive. Specify only one."); exit(1); } /* set up circle */ switch(cf.circle_mode) { default: case HITS_CIRCLE: hc = new Hits_Circle; hc->create(cf.circle_size); c = hc; break; case TIMED_CIRCLE: tc = new Timed_Circle; tc->create(cf.circle_size); c = tc; break; } /* set up one of each parser */ /* CommonLogParser handles combined too */ cmmp = new CommonLogParser; #if 0 /* this isn't written yet */ atpp = new AtopLogParser; #endif /* maps auto-resize, just use a sane starting point {{{ */ /* url string -> url hash map */ um = new map; /* hashing happens internally */ um->create(cf.circle_size); /* referrer string -> hash map */ rm = new map; rm->create(cf.circle_size); /* ip string -> ip hash map */ im = new map; im->create(cf.circle_size); /* host string -> host hash map */ hm = new map; hm->create(cf.circle_size); /* }}} */ memset(&gstats, 0, sizeof(gstats)); gstats.start = time(NULL); signal(SIGINT, &catchsig); signal(SIGKILL, &catchsig); signal(SIGWINCH, &catchwinch); win = initscr(); nonl(); /* no NL->CR/NL on output */ cbreak(); /* enable reading chars one at a time */ noecho(); keypad(win, true); nodelay(win, true); for( ;; ) { if (cf.exit) break; now = time(NULL); /* periodically check that we're not in a submenu, and we * haven't been for more than 5 seconds without any more * keypresses; but if cf.in_submenu_stay is true, we stay in * the submenu until we receive a keypress (in read_key) */ if (cf.in_submenu && !cf.in_submenu_stay && now - cf.in_submenu_time > 5) { cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); } /* if we have any input files waiting to be opened, try to */ for (x = 0 ; x < MAX_INPUT_FILES ; ++x) { /* for each entry in input .. */ curfile = &in[x]; if (curfile->inode && curfile->open == false) { dprintf("%s needs reopening\n", curfile->filename); new_file(curfile->filename, NO_SEEK_TO_END); /* what happens if a new fd is used? the old * struct will still have inode > 0 and * open == false so we'll keep coming back * here */ } } #if HAVE_ADNS_H /* see if adns has got anything for us */ collect_dns_responses(); #endif /* see if it's time to update.. */ if (display(last_display)) { /* returns true when we updated, so.. */ last_display = now; // remember our last update } /* start of for() loop within these folds.. */ #if (POLLING_METHOD == USING_KQUEUE) /* {{{ */ /* every 1/10th of a second */ stv.tv_sec = 0; stv.tv_nsec = 10000000; x = kevent(kq, NULL, 0, ev, MAX_INPUT_FILES, &stv); // if (x == 0) continue; /* timeout, nothing happened */ if (x < 0) break; /* error */ for(evptr = ev ; evptr->filter ; evptr++) { /* udata contains a pointer to the struct input ** array element for this fd */ curfile = (struct input *)evptr->udata; fd = curfile->fd; /* or evptr->ident will work */ /* see if we can deduce what happened */ if (((evptr->fflags & NOTE_DELETE) == NOTE_DELETE) || ((evptr->fflags & NOTE_RENAME) == NOTE_RENAME)) { /* file deleted or renamed */ close(fd); curfile->open = false; /* skip rest of loop, we'll try * to reopen at top of for(;;) */ continue; } else if ((evptr->fflags & NOTE_WRITE) == NOTE_WRITE) { //read_amt = ev.data; /* we don't use this */ } /* }}} */ #elif (POLLING_METHOD == USING_FAM) /* {{{ */ FD_ZERO(&readfds); FD_SET(fam_fd, &readfds); /* 1/10th of a second */ tv.tv_sec = 0; tv.tv_usec = 100000; /* perform a select() on the fam filedescriptor */ select(fam_fd + 1, &readfds, NULL, NULL, &tv); /* if FAM has nothing to report, loop around */ if (!(FD_ISSET(fam_fd, &readfds))) continue; while (FAMPending(&fc) == 1 && FAMNextEvent(&fc, &fe)) { /* fe.userdata is struct input * element for this ** file; see new_file() for how it's set */ curfile = (struct input *)fe.userdata; fd = curfile->fd; /* }}} */ #else /* fallback to stat() {{{ */ /* 1/10th of a second */ tv.tv_sec = 0; tv.tv_usec = 100000; select(0, NULL, NULL, NULL, &tv); /* check every file */ for (x = 0 ; x < MAX_INPUT_FILES ; ++x) { curfile = &in[x]; fd = curfile->fd; if (curfile->open == false || fd == 0) continue; if (stat(curfile->filename, &sb) == -1) { /* file removed */ close(fd); curfile->fd = 0; curfile->open = false; /* skip rest of loop, we'll try * to reopen at top of for(;;) */ continue; } else { /* file still there, but lets check the * inode hasn't changed (ie, it's been * recreated under our feet */ if (sb.st_ino != curfile->inode) { close(fd); curfile->open = false; /* skip rest of loop, we'll try * to reopen at top of for(;;) */ continue; } } #endif /* }}} */ /* read the data */ buflen = read(fd, buf, sizeof(buf)); if (buflen == 0) /* no data */ { /* this should only happen if we've had to * fall back to stat() */ continue; } if (buflen < 0) /* error */ { DIE("read"); } curfile->lastreq = now; /* pass each line to logsplit; FIXME tidy this up */ nextline = bufcp = buf; while ((nextline - buf) < buflen && nextline) { bufcp = nextline; /* find the end of this line */ if (!(nextline = strchr(bufcp, '\n'))) { /* we can't, ignore it */ continue; } *nextline = '\0'; ++nextline; /* which parser? */ #if CHRIS_HAS_WRITTEN_MORE_PARSERS switch(in->type) { /* only one atm */ default: p = cmmp; break; } #else p = cmmp; #endif if (p->parse(bufcp, &lb) == 0) { /* record which file the log is from */ lb.fileid = fd; /* insert into circle */ c->insert(lb); /* record stats */ recordstats(lb); } // next line.. } /* while ((nl - buf) < buflen && nl) */ } /* for(evptr = ev ; evptr->filter ; evptr++) */ /* while (FAMPending(&fc) == 1 && FAMNextEvent(&fc, &fe)) */ /* for (i = 0 ; i < MAX_INPUT_FILES ; ++i) */ /* check for keypresses */ if ((ch = wgetch(win)) != ERR) read_key(ch); } /* for( ;; ) */ delete cmmp; endwin(); return 0; } int recordstats(struct logbits l) /* {{{ */ { int t; /* global stats */ if (gstats.alltime.first == 0) gstats.alltime.first = now; gstats.alltime.last = now; gstats.alltime.reqcount++; gstats.alltime.bytecount += l.bytes; /* for this return code, increment */ t = (int)(l.retcode/100); gstats.r_codes[t].reqcount++; gstats.r_codes[t].bytecount += l.bytes; return 0; } /* }}} */ int read_key(int ch) /* {{{ */ { #define SUBMENU_SORT_HB_TITLE "sort by.." #define SUBMENU_SORT_HB_BANNER "r) REQUESTS R) REQS/SEC b) BYTES B) BYTES/SEC" #define SUBMENU_SORT_RC_TITLE "sort by.." #define SUBMENU_SORT_RC_BANNER "2) 2xx 3) 3xx 4) 4xx 5) 5xx" #define SUBMENU_DISP_TITLE "toggle subdisplay.." #define SUBMENU_DISP_BANNER "u) URLS r) REFERRERS h) HOSTS" #define SUBMENU_FILT_TITLE "filters.." #define SUBMENU_FILT_BANNER "a) add/edit menu c) clear all s) show active" #define SUBMENU_FILT_ADD_TITLE "filters: add.." #define SUBMENU_FILT_ADD_BANNER "u) to URLS r) to REFERRERS h) to HOSTS" /* got a keypress, best process it */ /* check submenu state; in no submenu we do the master list.. */ if (cf.in_submenu == SUBMENU_NONE) /* {{{ */ switch(ch) { case 's': /* sort .. submenus */ cf.in_submenu_time = now; cf.in_submenu_stay = false; switch(cf.numbers_mode) { default: case NUMBERS_HITS_BYTES: cf.in_submenu = SUBMENU_SORT_HB; /* display banner to aid further * presses */ display_submenu_banner( SUBMENU_SORT_HB_TITLE, sizeof(SUBMENU_SORT_HB_TITLE), SUBMENU_SORT_HB_BANNER); break; case NUMBERS_RETCODES: cf.in_submenu = SUBMENU_SORT_RC; /* display banner to aid further * presses */ display_submenu_banner( SUBMENU_SORT_RC_TITLE, sizeof(SUBMENU_SORT_RC_TITLE), SUBMENU_SORT_RC_BANNER); break; } break; case 't': /* toggle displays */ cf.in_submenu = SUBMENU_DISP; cf.in_submenu_time = now; cf.in_submenu_stay = false; /* display banner to aid further presses */ display_submenu_banner(SUBMENU_DISP_TITLE, sizeof(SUBMENU_DISP_TITLE), SUBMENU_DISP_BANNER); break; case 'f': /* filter submenu */ cf.in_submenu = SUBMENU_FILT; cf.in_submenu_time = now; cf.in_submenu_stay = false; /* display banner to aid further presses */ display_submenu_banner(SUBMENU_FILT_TITLE, sizeof(SUBMENU_FILT_TITLE), SUBMENU_FILT_BANNER); break; case 'h': /* enter help; this is a special submenu */ case '?': cf.in_submenu = SUBMENU_HELP; cf.in_submenu_time = now; /* stay in here till next keypress */ cf.in_submenu_stay = true; /* don't blat it */ cf.display_paused = true; display_help(); break; case 'p': /* (un)pause display */ cf.display_paused = !cf.display_paused; if (cf.display_paused) { /* tell the user we're paused */ DRAW_PAUSED(0,60); /* macro in display.h */ refresh(); } else { /* turning off pause forces an update */ cf.do_immed_display = true; } break; case ' ': cf.do_immed_display = true; break; #if 0 case 's': /* new refresh delay */ #ifndef SOLARIS /* linking against readline is failing on Solaris atm */ endwin(); char *t; int nd; t = readline("Seconds to Delay: "); if ((nd = atoi(t)) != 0) cf.refresh_delay = nd; refresh(); #endif break; #endif case 'd': /* display mode; urls or hosts */ switch(cf.display_mode) { case DISPLAY_URLS: cf.display_mode = DISPLAY_HOSTS; break; case DISPLAY_HOSTS: cf.display_mode = DISPLAY_REFS; break; case DISPLAY_REFS: cf.display_mode = DISPLAY_URLS; break; } cf.do_immed_display = true; break; case 'n': /* numbers mode; hits/bytes or returncodes */ switch(cf.numbers_mode) { case NUMBERS_HITS_BYTES: cf.numbers_mode = NUMBERS_RETCODES; break; case NUMBERS_RETCODES: cf.numbers_mode = NUMBERS_HITS_BYTES; break; } cf.do_immed_display = true; break; case KEY_UP: if (cf.display_mode != DISPLAY_DETAIL) { /* sanity checking for this is done * in drawMarker() */ cf.selected_item_screen--; drawMarker(); translate_screen_to_pos(); } break; #if 0 case KEY_END: case KEY_NPAGE: if (cf.display_mode != DISPLAY_DETAIL) { cf.selected_item_screen = 9999; drawMarker(); translate_screen_to_pos(); } break; #endif case KEY_DOWN: if (cf.display_mode != DISPLAY_DETAIL) { /* sanity checking for this is done * in drawMarker() */ cf.selected_item_screen++; drawMarker(); translate_screen_to_pos(); } break; case KEY_RIGHT: if (cf.display_mode != DISPLAY_DETAIL) { /* enter detailed display mode */ cf.display_mode_detail = cf.display_mode; cf.display_mode = DISPLAY_DETAIL; cf.do_immed_display = true; } break; case KEY_LEFT: if (cf.display_mode == DISPLAY_DETAIL) { /* leave detailed display mode */ cf.display_mode = cf.display_mode_detail; cf.do_immed_display = true; } break; case KEY_REFRESH: refresh(); break; case KEY_BREAK: case 'q': cf.exit = true; break; } /* }}} */ else if (cf.in_submenu == SUBMENU_SORT_HB) /* {{{ */ { switch(ch) { case 'r': cf.sort = SORT_REQCOUNT; break; case 'R': cf.sort = SORT_REQPERSEC; break; case 'b': cf.sort = SORT_BYTECOUNT; break; case 'B': cf.sort = SORT_BYTESPERSEC; break; } cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); cf.do_immed_display = true; } /* }}} */ else if (cf.in_submenu == SUBMENU_SORT_RC) /* {{{ */ { switch(ch) { case '2': cf.retcodes_sort = SORT_2XX; break; case '3': cf.retcodes_sort = SORT_3XX; break; case '4': cf.retcodes_sort = SORT_4XX; break; case '5': cf.retcodes_sort = SORT_5XX; break; } cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); cf.do_immed_display = true; } /* }}} */ else if (cf.in_submenu == SUBMENU_DISP) /* {{{ */ { switch(ch) { case 'u': cf.detail_display_urls = !cf.detail_display_urls; break; case 'r': cf.detail_display_refs = !cf.detail_display_refs; break; case 'h': case 'i': cf.detail_display_hosts = !cf.detail_display_hosts; break; } cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); cf.do_immed_display = true; } /* }}} */ else if (cf.in_submenu == SUBMENU_FILT) /* {{{ */ switch(ch) { default: /* default action is to back out */ cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); cf.do_immed_display = true; break; case 'a': /* add filter.. */ /* clean away existing menu */ clear_submenu_banner(); cf.in_submenu = SUBMENU_FILT_ADD; cf.in_submenu_time = now; cf.in_submenu_stay = false; /* display banner to aid further presses */ display_submenu_banner(SUBMENU_FILT_ADD_TITLE, sizeof(SUBMENU_FILT_ADD_TITLE), SUBMENU_FILT_ADD_BANNER); break; case 'c': /* clear all filters */ cf.urlfilter->empty(); cf.hostfilter->empty(); cf.reffilter->empty(); cf.in_submenu = SUBMENU_NONE; clear_submenu_banner(); cf.do_immed_display = true; break; case 's': /* show filter page */ cf.in_submenu = SUBMENU_FILT_SHOW; cf.in_submenu_time = now; /* stay in here till next keypress */ cf.in_submenu_stay = true; /* don't blat it */ cf.display_paused = true; /* and render the page; similar to display_help(); * this calls functions in the Filter class to do * its work */ display_active_filters(); break; } /* }}} */ else if (cf.in_submenu == SUBMENU_FILT_ADD) /* {{{ */ { char *input = NULL; /* check the keypress was valid .. */ switch(ch) { case 'u': case 'r': case 'h': /* it was */ break; default: cf.in_submenu = SUBMENU_NONE; cf.in_submenu_stay = false; clear_submenu_banner(); cf.do_immed_display = true; return 0; break; } /* get an expression */ /* do not leave until readline is done */ cf.in_submenu_stay = true; /* do not update the display, because we're endwin()'ed */ cf.display_paused = true; endwin(); input = readline("Filter: "); /* back into curses mode */ refresh(); nonl(); /* no NL->CR/NL on output */ cbreak(); /* enable reading chars one at a time */ noecho(); /* update again */ cf.in_submenu = SUBMENU_NONE; cf.in_submenu_stay = false; cf.display_paused = false; cf.do_immed_display = true; if (!(input && *input)) { return 0; } add_history(input); /* apply to the appropriate filter */ switch(ch) { case 'u': cf.urlfilter->store(input); break; case 'h': cf.hostfilter->store(input); break; case 'r': cf.reffilter->store(input); break; } free(input); } /* }}} */ /* special submenus which take over the entire screen */ else if (cf.in_submenu == SUBMENU_HELP || cf.in_submenu == SUBMENU_FILT_SHOW) /* {{{ */ { /* any key in these special "submenus" exits them */ cf.in_submenu = SUBMENU_NONE; cf.in_submenu_stay = false; cf.display_paused = false; cf.do_immed_display = true; } /* }}} */ return 0; } /* }}} */ int new_file(const char *filename, bool do_seek_to_end) /* {{{ */ /* opens the filename supplied, * and fills in the struct input passed by reference */ { int i, fd, input_element = -1; struct stat sb; struct input *this_file; char realfile[MAXPATHLEN]; /* if realpath cannot resolve the file, give up */ if ((realpath(filename, realfile) == NULL)) return -1; #if (POLLING_METHOD == USING_KQUEUE) struct kevent kev; #endif /* stat the item; ensure it's a file and get an inode */ if (stat(realfile, &sb) == -1) return -1; /* we can't tail anything except a file */ // if (sb.st_mode != S_IFREG) // return -1; /* choose where to put this input in the struct */ /* if it has an existing slot, re-use */ for(i = 0 ; i < cf.input_count ; ++i) { if ((strcmp(in[i].filename, filename)) == 0) { input_element = i; break; } } if (input_element == -1) { /* new open, make sure we have room in our arrays */ if (cf.input_count == MAX_INPUT_FILES) { DIE_N("Only 50 files are supported at the moment"); } /* add it on at the end */ input_element = cf.input_count; } if ((fd = open(realfile, O_RDONLY)) == -1) return -1; this_file = &in[input_element]; this_file->fd = fd; if (do_seek_to_end) lseek(fd, 0, SEEK_END); if (this_file->filename) free(this_file->filename); this_file->inode = sb.st_ino; this_file->filename = strdup(realfile); this_file->lastreq = now; this_file->type = LOG_COMMON; /* assumption */ this_file->open = true; #if (POLLING_METHOD == USING_KQUEUE) /* add into kqueue */ #ifdef __NetBSD__ EV_SET(&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE | NOTE_DELETE | NOTE_RENAME, 0, (intptr_t)this_file); #else EV_SET(&kev, fd, EVFILT_VNODE, EV_ADD | EV_ENABLE | EV_CLEAR, NOTE_WRITE | NOTE_DELETE | NOTE_RENAME, 0, this_file); #endif if (kevent(kq, &kev, 1, NULL, 0, NULL) < 0) { DIE("cannot create kevent"); } #elif (POLLING_METHOD == USING_FAM) /* add into FAM */ FAMMonitorFile(&fc, realfile, &this_file->famreq, this_file); #endif return 0; } /* }}} */ void version(void) /* {{{ */ { fprintf(stdout, "ApacheTop %s\n", PACKAGE_VERSION); } /* }}} */ void usage(void) /* {{{ */ { fprintf(stderr, "ApacheTop v%s - Usage:\n" "File options:\n" " -f logfile open logfile (assumed common/combined) [%s]\n" " (repeat option for more than one source)\n" "\n" "URL/host/referrer munging options:\n" " -q keep query strings [%s]\n" " -l lowercase all URLs [%s]\n" " -s num keep num path segments of URL [all]\n" " -p preserve protocol at front of referrers [%s]\n" " -r resolve hostnames/IPs into each other [%s]\n" "\n" "Stats options:\n" " Supply up to one of the following two. default: [-%c %d]\n" " -H hits remember stats for this many hits\n" " -T secs remember stats for this many seconds\n" "\n" " -d secs refresh delay in seconds [%d]\n" "\n" " -v show version\n" " -h this help\n" "\n" "Compile Options: %cHAVE_KQUEUE %cHAVE_FAM %cENABLE_PCRE\n" "Polling Method: %s\n" , PACKAGE_VERSION, DEFAULT_LOGFILE, /* cf is set by the time we get to usage, so we can use contents */ cf.keep_querystring ? "yes" : "no", cf.lowercase_urls ? "yes" : "no", cf.preserve_ref_protocol ? "yes" : "no", cf.do_resolving ? "yes" : "no", cf.circle_mode, cf.circle_size, cf.refresh_delay, #if HAVE_KQUEUE /* {{{ */ '+', #else '-', #endif /* }}} */ #if HAVE_FAM_H /* {{{ */ '+', #else '-', #endif /* }}} */ #if HAVE_PCRE_H /* {{{ */ '+', #else '-', #endif /* }}} */ (POLLING_METHOD == USING_KQUEUE ? "kqueue" : (POLLING_METHOD == USING_FAM ? "fam" : "stat" ) ) ); return; } /* }}} */ int dprintf(const char *fmt, ...) /* {{{ */ { FILE *d; va_list args; static char fileName[1024] = {'\0'}; if ( !strlen( fileName ) ) { strcpy( fileName, "/tmp/atop.XXXXXX" ); mkdtemp( fileName ); strncat( fileName, "/debug", sizeof(fileName) - strlen(fileName) - 1 ); } if (cf.debug && (d = fopen(fileName, "a"))) { va_start(args, fmt); vfprintf(d, fmt, args); fclose(d); va_end(args); } return 0; } /* }}} */ static void catchsig(int s) /* {{{ */ { cf.exit = s; } /* }}} */ /* handle a window resize by simply reopening and redrawing our window */ static void catchwinch(int s) { endwin(); refresh(); cf.do_immed_display = true; } apachetop-0.19.7/src/apachetop.h000066400000000000000000000135241351455032200164650ustar00rootroot00000000000000#ifndef _APACHETOP_H_ #define _APACHETOP_H_ #if HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #if HAVE_STRINGS_H # include #endif #include #include #include #include #include #include #include #if TIME_WITH_SYS_TIME # include # include #else # if HAVE_SYS_TIME_H # include # else # include # endif #endif #if HAVE_SYS_SOCKET_H # include #endif #if HAVE_NETINET_IN_H # include #endif #if HAVE_ARPA_INET_H # include #endif #if HAVE_NETDB_H # include #endif #if HAVE_PCRE_H # include # include "pcre_cpp_wrapper.h" #endif #include #include #include /* Use kqueue in preference to anything else. * If we don't have that, try FAM * If we don't have FAM, fall back to stat() */ #define USING_KQUEUE 1 #define USING_FAM 2 #define USING_STAT 3 #if HAVE_KQUEUE # include # define POLLING_METHOD USING_KQUEUE #elif HAVE_FAM_H # include # define POLLING_METHOD USING_FAM #endif /* stat() fallback */ #ifndef POLLING_METHOD # define POLLING_METHOD USING_STAT #endif #if HAVE_ADNS_H # include #endif #define getMIN(a,b) (a < b ? a : b) #define getMAX(a,b) (a > b ? a : b) #ifdef HAVE_SYS_PARAM_H # include #endif /* last resort */ #ifndef MAXPATHLEN # define MAXPATHLEN 1024 #endif /* upon startup, each input file is put into an element of this array, * starting at 0. The struct under this maps the current fd into this array * so we can find it without iterating. */ struct input { char *filename; int fd; ino_t inode; short type; /* types defined in log.h */ time_t lastreq; /* if open == false, then ApacheTop will periodically try to re-open * this input file. */ bool open; /* is the file open or not? */ #if (POLLING_METHOD == USING_FAM) FAMRequest famreq; #endif }; #include "filters.h" struct config { #define SORT_REQCOUNT 1 #define SORT_REQPERSEC 2 #define SORT_BYTECOUNT 3 #define SORT_BYTESPERSEC 4 #define SORT_2XX 12 /* see display.cc around line 911 (shellsort) */ #define SORT_3XX 13 /* for the consequences of changing these. */ #define SORT_4XX 14 /* Logic there relies on them being */ #define SORT_5XX 15 /* (retcode/100)+SORTTYPE_OFFSET_HACK */ #define SORTTYPE_OFFSET_HACK 10 short sort, retcodes_sort; short refresh_delay; short do_resolving; short selected_item_screen; /* screen position our marker is at */ unsigned int selected_item_pos; /* which url/ip/refpos it relates to */ short selected_item_mode; /* is it url/ip/ref pos? */ unsigned int selected_item_hash; short current_display_size; /* how many lines we're displaying */ short input_count; #define TIMED_CIRCLE 'T' #define HITS_CIRCLE 'H' int circle_mode; int circle_size; /* these defines are used in cf.filter too */ #define DISPLAY_URLS 1 #define DISPLAY_HOSTS 2 #define DISPLAY_REFS 3 #define DISPLAY_FILES 4 #define DISPLAY_DETAIL 9 short display_mode; #define NUMBERS_HITS_BYTES 1 #define NUMBERS_RETCODES 2 short numbers_mode; /* when in DISPLAY_DETAIL mode, display_mode is copied in here */ short display_mode_detail; /* what would we like to display in DISPLAY_DETAIL modes? */ bool detail_display_hosts, detail_display_refs, detail_display_urls; bool display_paused; bool do_immed_display; /* signals a screen update is req'd */ /* filters */ Filter *urlfilter, *hostfilter, *reffilter; /* url munging */ bool keep_querystring; bool lowercase_urls; unsigned short keep_segments; bool preserve_ref_protocol; bool debug; bool exit; /* true when we want to finish */ /* keypress submenu */ #define SUBMENU_NONE 0 #define SUBMENU_SORT_HB 1 #define SUBMENU_SORT_RC 2 #define SUBMENU_DISP 4 #define SUBMENU_FILT 5 #define SUBMENU_FILT_ADD 6 #define SUBMENU_FILT_SHOW 7 #define SUBMENU_HELP 9 unsigned short in_submenu; bool in_submenu_stay; /* stay in submenu till keypress? */ time_t in_submenu_time; }; struct hitinfo { double bytecount; double reqcount; time_t first, last; /* for stats worked out in display.cc */ float rps, bps; }; struct gstat { time_t start; /* when did we start */ /* space for 1xx-5xx return codes */ struct hitinfo r_codes[6]; /* space for counting global hits and bytes per sec */ struct hitinfo alltime; }; //#include "opt.h" #include "ohtbl.h" #if HAVE_ADNS_H # include "resolver.h" #endif #include "map.h" #include "circle.h" #include "hits_circle.h" #include "timed_circle.h" #include "display.h" #include "log.h" #include "queue.h" #define JAN 281 #define FEB 269 #define MAR 288 #define APR 291 #define MAY 295 #define JUN 301 #define JUL 299 #define AUG 285 #define SEP 296 #define OCT 294 #define NOV 307 #define DEC 268 #define DEBUG_OUTPUT "/tmp/atop.debug" /* this can be overridden from config.h via ./configure --with-logfile .. */ #ifndef DEFAULT_LOGFILE # define DEFAULT_LOGFILE "/var/log/access_log" #endif #define DEFAULT_CIRCLE_SIZE 30 #define DEFAULT_CIRCLE_MODE TIMED_CIRCLE #define DEFAULT_SORT SORT_REQCOUNT #define DEFAULT_RETCODES_SORT SORT_2XX #define DEFAULT_REFRESH_DELAY 5 #define DEFAULT_DISPLAY_MODE DISPLAY_URLS #define DEFAULT_NUMBERS_MODE NUMBERS_HITS_BYTES /* if the layout of the display changes, these need updating */ #define COLS_RESERVED 25 #define LINES_RESERVED 7 #define MAX_INPUT_FILES 50 int recordstats(struct logbits l); int read_key(int ch); #define SEEK_TO_END true #define NO_SEEK_TO_END false int new_file(const char *filename, bool do_seek_to_end); void usage(void); void version(void); int dprintf(const char *fmt, ...); static void catchsig(int s); static void catchwinch(int s); #endif apachetop-0.19.7/src/circle.h000066400000000000000000000006241351455032200157570ustar00rootroot00000000000000#ifndef _CIRCLE_H_ #define _CIRCLE_H_ class Circle { public: virtual int create(unsigned int size) = 0; virtual int insert(struct logbits lb) = 0; virtual int walk(struct logbits **lb) = 0; virtual time_t oldest(void) = 0; virtual void updatestats(void) = 0; virtual double getreqcount(void) = 0; virtual double getbytecount(void) = 0; virtual double getsummary(int r_c) = 0; }; #endif apachetop-0.19.7/src/display.cc000066400000000000000000000742501351455032200163270ustar00rootroot00000000000000#include "apachetop.h" #include "inlines.cc" extern struct gstat gstats; extern time_t now; extern Circle *c; extern map *um, /* urlmap */ *im, /* ipmap */ *hm, /* hostmap */ *rm, /* referrermap */ *fm; /* filemap */ extern struct config cf; /* global, so we can keep it after display() finishes; the only reason for * this so far is so we know what's being displayed after this function * finishes. Hence we can translate selected_item_screen into a * selected_item_pos when the marker moves */ extern itemlist *items; extern map *last_display_map; bool display(time_t last_display) /* {{{ */ { struct gstat *w_gstats; Circle *w_c; if (cf.do_immed_display) { /* we've been asked to update immediately, whether we're * paused or not */ /* this flag enforces a clear() (why? forgot now) */ clear(); /* clear that flag */ cf.do_immed_display = false; } else if ( /* do nothing if display is paused */ cf.display_paused || /* or it's not time to update yet */ (now - last_display) < cf.refresh_delay ) return false; /* if we reach here, we're fine to update */ display_header(); //display_histogram(); //return true; if (cf.display_mode != DISPLAY_DETAIL) { /* normal display */ display_list(); return true; } /* detailed display mode involves showing the item the user is * interested in (complete stats, whether it be an URL or whatever), * followed by alternate stats. Example. The user is interested in * one particular URL, so we show that URL as it would be in * DISPLAY_URLS, then split the rest of the screen between IPs * hitting that URL, and referrers referring to that URL */ /* first line is the pos of interest */ display_list(); /* now split the screen with all other stats */ /* one line for header, one for main stat */ #define FIRST_OFFSET 2 /* figure out how much room we have */ int size, cur_offset, num_sections; cur_offset = FIRST_OFFSET; switch(cf.display_mode_detail) { case DISPLAY_URLS: /* show IPs and Referrers */ num_sections = (cf.detail_display_hosts ? 1 : 0) + (cf.detail_display_refs ? 1 : 0); /* anything to do? */ if (num_sections == 0) break; size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) / num_sections); if (cf.detail_display_hosts) { display_sub_list(DISPLAY_HOSTS,cur_offset,size); cur_offset += size + 1; } if (cf.detail_display_refs) { display_sub_list(DISPLAY_REFS,cur_offset,size); cur_offset += size + 1; } break; case DISPLAY_HOSTS: /* show URLs and Referrers */ num_sections = (cf.detail_display_urls ? 1 : 0) + (cf.detail_display_refs ? 1 : 0); /* anything to do? */ if (num_sections == 0) break; size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) / num_sections); if (cf.detail_display_urls) { display_sub_list(DISPLAY_URLS,cur_offset,size); cur_offset += size + 1; } if (cf.detail_display_refs) { display_sub_list(DISPLAY_REFS,cur_offset,size); cur_offset += size + 1; } break; case DISPLAY_REFS: /* show URLs and IPs */ num_sections = (cf.detail_display_urls ? 1 : 0) + (cf.detail_display_hosts ? 1 : 0); /* anything to do? */ if (num_sections == 0) break; size = (((LINES-LINES_RESERVED-2) - FIRST_OFFSET) / num_sections); if (cf.detail_display_urls) { display_sub_list(DISPLAY_URLS,cur_offset,size); cur_offset += size + 1; } if (cf.detail_display_hosts) { display_sub_list(DISPLAY_HOSTS,cur_offset,size); cur_offset += size + 1; } break; } refresh(); return true; } /* }}} */ void display_header() /* {{{ */ { int itmp; float bytes, bps, per_req, ftmp; unsigned int secs_offset, diff, d = 0, h = 0, m = 0, s = 0; char bytes_suffix, bps_suffix, per_req_suffix; move(0, 0); clrtoeol(); /* last hit */ secs_offset = gstats.alltime.last % 86400; mvprintw(0, 0, "last hit: %02d:%02d:%02d", secs_offset / 3600, (secs_offset / 60) % 60, secs_offset % 60); /* uptime */ diff = (unsigned int)difftime(now, gstats.start); if (diff > 86399) diff -= ((d = diff / 86400)*86400); if (diff > 3599) diff -= ((h = diff / 3600)*3600); if (diff > 59) diff -= ((m = diff / 60)*60); s = diff; mvprintw(0, 27, "atop runtime: %2d days, %02d:%02d:%02d", d, h, m, s); /* are we paused? */ if (cf.display_paused) { DRAW_PAUSED(0,60); /* macro in display.h */ } /* current time */ secs_offset = now % 86400; mvprintw(0, 71, "%02d:%02d:%02d", secs_offset /3600, (secs_offset/ 60) % 60, secs_offset % 60); //All: 1,140,532 requests (39.45/sec), 999,540,593 bytes (857,235/sec) ftmp = getMAX(now-gstats.alltime.first, 1); /* divide-by-zero hack */ bytes = readableNum(gstats.alltime.bytecount, &bytes_suffix); bps = readableNum(gstats.alltime.bytecount/ftmp, &bps_suffix); per_req = readableNum( gstats.alltime.bytecount/getMAX(gstats.alltime.reqcount, 1), &per_req_suffix); attron(A_BOLD); mvprintw(1, 0, "All: %12.0f reqs (%6.1f/sec) %11.1f%c (%7.1f%c/sec) %7.1f%c/req", gstats.alltime.reqcount, gstats.alltime.reqcount/ftmp, bytes, bytes_suffix, bps, bps_suffix, per_req, per_req_suffix); attroff(A_BOLD); // 2xx 1,604,104 (95%) 3xx 1,000,000 ( 3%) 4xx 1,000,000 ( 1%) // 5xx 1,000,000 ( 1%) ftmp = gstats.r_codes[2].reqcount + gstats.r_codes[3].reqcount+ gstats.r_codes[4].reqcount + gstats.r_codes[5].reqcount; if (ftmp == 0) ftmp = 1; /* avoid NaN with no hits */ mvprintw(2, 0, "2xx: %7.0f (%4.*f%%) 3xx: %7.0f (%4.*f%%) " "4xx: %5.0f (%4.*f%%) 5xx: %5.0f (%4.*f%%) ", gstats.r_codes[2].reqcount, (gstats.r_codes[2].reqcount/ftmp) == 1 ? 0 : 1, (gstats.r_codes[2].reqcount/ftmp)*100, gstats.r_codes[3].reqcount, (gstats.r_codes[3].reqcount/ftmp) == 1 ? 0 : 1, (gstats.r_codes[3].reqcount/ftmp)*100, gstats.r_codes[4].reqcount, (gstats.r_codes[4].reqcount/ftmp) == 1 ? 0 : 1, (gstats.r_codes[4].reqcount/ftmp)*100, gstats.r_codes[5].reqcount, (gstats.r_codes[5].reqcount/ftmp) == 1 ? 0 : 1, (gstats.r_codes[5].reqcount/ftmp)*100 ); /* housecleaning on the circle, if its required in this class */ c->updatestats(); /* fetch the time of the first "recent" request */ itmp = now - c->oldest(); itmp = getMAX(itmp, 1); /* divide-by-zero hack */ bytes = readableNum(c->getbytecount(), &bytes_suffix); bps = readableNum(c->getbytecount()/itmp, &bps_suffix); per_req = readableNum(c->getbytecount()/getMAX(c->getreqcount(), 1), &per_req_suffix); attron(A_BOLD); mvprintw(3, 0, "R (%3ds): %7.0f reqs (%6.1f/sec) %11.1f%c (%7.1f%c/sec) %7.1f%c/req", itmp, c->getreqcount(), ((float)c->getreqcount()/itmp), bytes, bytes_suffix, bps, bps_suffix, per_req, per_req_suffix ); attroff(A_BOLD); ftmp = c->getsummary(2) + c->getsummary(3) + c->getsummary(4) + c->getsummary(5); if (ftmp == 0) ftmp = 1; /* avoid NaN with no hits */ mvprintw(4, 0, "2xx: %7.0f (%4.*f%%) 3xx: %7.0f (%4.*f%%) " "4xx: %5.0f (%4.*f%%) 5xx: %5.0f (%4.*f%%) ", c->getsummary(2), (c->getsummary(2)/ftmp) == 1 ? 0 : 1, (c->getsummary(2)/ftmp)*100, c->getsummary(3), (c->getsummary(3)/ftmp) == 1 ? 0 : 1, (c->getsummary(3)/ftmp)*100, c->getsummary(4), (c->getsummary(4)/ftmp) == 1 ? 0 : 1, (c->getsummary(4)/ftmp)*100, c->getsummary(5), (c->getsummary(5)/ftmp) == 1 ? 0 : 1, (c->getsummary(5)/ftmp)*100 ); // mvprintw(5, 0, // "Unique Objects: Size Footprint:"); /* if any filters are active, and the user is not in a submenu, * display a summary */ if (cf.in_submenu == SUBMENU_NONE) { int y; char active_filters[20]; bzero(active_filters, sizeof(active_filters)); /* suss out which filters are active */ if (cf.urlfilter->isactive()) strcat(active_filters, "URLs "); if (cf.hostfilter->isactive()) strcat(active_filters, "HOSTs "); if (cf.reffilter->isactive()) strcat(active_filters, "REFs "); if (*active_filters) { y = COLS - (strlen(active_filters) + 11); mvprintw(SUBMENU_LINE_NUMBER, y, "Filtering: %s", active_filters); } } } /* }}} */ void display_list() /* {{{ */ { int x, t, xx; char *cptmp; struct config scf; /* for copying cf, see comment below at memcpy */ OAHash item_hash; int item_used = 0, disp; double items_size; struct itemlist *item_ptr = NULL; /* for easy reference during walk() */ map *map; unsigned int hash; int pos; /* walk() pointer */ struct logbits *lb; /* make an array containing all we have, then sort it */ items_size = c->getreqcount(); if (items_size == 0) { /* nothing to do! */ move(LINES_RESERVED-1, 0); clrtobot(); refresh(); return; } item_hash.create( (int)items_size * 5 ); /* this is our array; the cast is because items_size is a double but * I'm fairly sure, realistically, it'll never get high enough to be * a problem, so uInt should be ok */ if (items) free(items); /* get rid of the last one */ items = (struct itemlist *) calloc((unsigned int)items_size, sizeof(itemlist)); /* another thread may change the contents of cf while we're running, * and it would be undesirable to have most of this change on us, so * we make safe copies to use */ memcpy(&scf, &cf, sizeof(struct config)); /* if we are in detailed display mode, then we have to overwrite * scf.display_mode so all these switch()'es work */ if (scf.display_mode == DISPLAY_DETAIL) scf.display_mode = cf.display_mode_detail; /* pick a map to use */ switch(scf.display_mode) { default: case DISPLAY_URLS: map = um; break; case DISPLAY_HOSTS: map = hm; break; case DISPLAY_REFS: map = rm; break; case DISPLAY_FILES: map = fm; break; } /* store that in a global that we can remember */ last_display_map = map; /* walk the entire circle */ while(c->walk(&lb) != -1) { /* skip unused */ if (lb == NULL) continue; /* set up some pointers, depending on cf.display_mode, so * that we can refer to the url_pos or ip_pos via just pos, * and the urlmap or ipmap via just map */ switch(scf.display_mode) { default: case DISPLAY_URLS: hash = lb->url_hash; pos = lb->url_pos; break; case DISPLAY_HOSTS: hash = lb->host_hash; pos = lb->host_pos; break; case DISPLAY_REFS: hash = lb->ref_hash; pos = lb->ref_pos; break; #if HAVE_FILE_MODE_DISPLAY case DISPLAY_FILES: hash = lb->file_hash ; pos = lb->file_pos; break; #endif } /* FILTERS {{{ */ /* skip this item if it doesn't match our filter */ if (cf.urlfilter->isactive() && !cf.urlfilter->match(um->reverse(lb->url_pos))) continue; if (cf.hostfilter->isactive() && !cf.hostfilter->match(hm->reverse(lb->host_pos))) continue; if (cf.reffilter->isactive() && !cf.reffilter->match(rm->reverse(lb->ref_pos))) continue; /* }}} */ /* look up whatever string this pos is for */ cptmp = map->reverse(pos); /* then lookup that string in items */ item_ptr = (struct itemlist *)item_hash.lookup(cptmp); /* do we have this string already? */ if (item_ptr == NULL) { /* not seen it, make a new slot */ item_ptr = &items[item_used]; item_hash.insert(cptmp, item_ptr); item_ptr->item = pos; /* store the ip position in the itemlist too, just ** in case we have to look it up in show_map_line */ item_ptr->ip_item = lb->ip_pos; item_ptr->hash = hash; ++item_used; item_ptr->last = now; item_ptr->first = lb->time; } /* we have the string in items now, so update stats in array */ item_ptr->reqcount++; item_ptr->bytecount += lb->bytes; /* we wish to count up how many times a given retcode * has occurred for this item */ t = (int)(lb->retcode/100); item_ptr->r_codes[t].reqcount++; item_ptr->r_codes[t].bytecount += lb->bytes; if (lb->time < item_ptr->first) item_ptr->first = lb->time; } /* no further use for this hash */ item_hash.destroy(); /* calculate some stats; timespan, kbps, rps */ for(x = 0 ; x < item_used ; ++x) { item_ptr = &items[x]; item_ptr->timespan = item_ptr->last - item_ptr->first; if (item_ptr->timespan == 0) item_ptr->timespan = 1; /* hack to avoid /0 */ for(xx = 2 ; xx < 6 ; xx++) { item_ptr->r_codes[xx].rps = ((float) item_ptr->r_codes[xx].reqcount/item_ptr->timespan); item_ptr->r_codes[xx].bps = ((float) item_ptr->r_codes[xx].bytecount/item_ptr->timespan); } item_ptr->kbps = ((float) (item_ptr->bytecount/1024)/item_ptr->timespan); item_ptr->rps = ((float) item_ptr->reqcount/item_ptr->timespan); } move(SUBMENU_LINE_NUMBER+1, 0); clrtobot(); /* a suitable header, at LINES_RESERVED-1 * (-1 because curses starts at zero, but I've started at 1) */ switch(scf.numbers_mode) { case NUMBERS_HITS_BYTES: mvaddstr(LINES_RESERVED-1, 0, " REQS REQ/S KB KB/S"); break; case NUMBERS_RETCODES: mvaddstr(LINES_RESERVED-1, 0, " 2xx 3xx 4xx 5xx"); break; } /* what are we showing in the table? we have a few options; * * top $X URLS or IPs/Hosts or Referrers * detailed referrer stats for a given URL */ if (cf.display_mode == DISPLAY_DETAIL) { /* detailed referrer stats for a given URL */ // mvaddstr(LINES_RESERVED-1, 0, "REQS REQ/S KB KB/S"); /* display only the item we're interested in */ for (x = 0 ; x < item_used ; ++x) { item_ptr = &items[x]; if (item_ptr->hash == cf.selected_item_hash) { /* show stats for this line only */ show_map_line(item_ptr, 0, map, NO_INDENT, scf.numbers_mode); break; } } /* and return, display_sub_list can do the rest */ return; } /* top $X items; sort the array for display */ if (item_used) shellsort_wrapper(items, item_used, scf); /* display something */ switch(scf.display_mode) { case DISPLAY_URLS: mvaddstr(LINES_RESERVED-1, 23, "URL"); break; case DISPLAY_HOSTS: mvaddstr(LINES_RESERVED-1, 23, "HOST"); break; case DISPLAY_REFS: mvaddstr(LINES_RESERVED-1, 23, "REFERRER"); break; } disp = 0; /* count how many we've shown */ for(x = 0, item_ptr = &items[0] ; x < item_used && disp < LINES-LINES_RESERVED-1 ; ++item_ptr, ++x) { /* skip empty tablespaces, even though none should exist */ if (item_ptr->reqcount == 0) continue; /* render the line itself at position disp. map should be * already set from earlier in this function */ show_map_line(item_ptr, disp, map, NO_INDENT, scf.numbers_mode); ++disp; } /* translate the screen position (cf.selected_item_screen) into an * url/ip/ref_pos (cf.selected_item_pos), depending on current * display mode. We can then use selected_item_pos to get info for * the selected item if the user hits return to get it */ translate_screen_to_pos(); #if 0 /* debug line */ mvprintw(5,0, "%s", map->reverse(cf.selected_item_pos)); #endif cf.current_display_size = disp; /* if there were items, draw a marker */ if (cf.current_display_size) drawMarker(); else { /* come to rest just above the column headers */ move(LINES_RESERVED-2,0); refresh(); } } /* }}} */ void display_sub_list(short display_mode_override, /* {{{ */ unsigned short offset, unsigned short limit) { /* similar to display_list(), but this one just uses a section of * the screen for displaying information pertinent to a particular * URL or IP or REFERRER. */ /* I'd like to lose this code and replace the breakdown screen * with something more useful.. */ int x, t, xx; char *cptmp; unsigned int h; struct config scf; /* for copying cf, see comment below at memcpy */ OAHash item_hash; int item_used = 0, disp; double items_size; struct itemlist *subitems, *item_ptr = NULL; /* for easy reference during walk() */ map *map; unsigned int pos; /* walk() pointer */ struct logbits *lb; if (c->getreqcount() == 0) { /* nothing to do! */ refresh(); return; } /* make an array containing all we have, then sort it */ items_size = c->getreqcount(); item_hash.create( (int)items_size * 5 ); /* this is our array; the cast is because items_size is a double but * I'm fairly sure, realistically, it'll never get high enough to be * a problem, so uInt should be ok */ //if (items) free(items); /* get rid of the last one */ subitems = (struct itemlist *) calloc((unsigned int)items_size, sizeof(itemlist)); /* another thread may change the contents of cf while we're running, * and it would be undesirable to have most of this change on us, so * we make safe copies to use */ memcpy(&scf, &cf, sizeof(struct config)); /* pick a map to use */ switch(display_mode_override) { default: case DISPLAY_URLS: map = um; break; case DISPLAY_HOSTS: map = hm; break; case DISPLAY_REFS: map = rm; break; } /* walk the entire circle */ while(c->walk(&lb) != -1) { /* skip unused */ if (lb == NULL) continue; /* FILTERS? */ /* set up some pointers, depending on cf.display_mode, so * that we can refer to the url_pos or ip_pos via just pos, * and the urlmap or ipmap via just map */ switch(scf.display_mode_detail) { default: case DISPLAY_URLS: h = lb->url_hash; break; case DISPLAY_HOSTS: h = lb->host_hash; break; case DISPLAY_REFS: h = lb->ref_hash; break; } /* we're only interested in this circle item if it matches * the url/ip/referrer we're interested in; remember this is * a sub-list pertinent to one particular master item */ if (h != cf.selected_item_hash) continue; switch(display_mode_override) { default: case DISPLAY_URLS: pos = lb->url_pos; break; case DISPLAY_HOSTS: pos = lb->host_pos; break; case DISPLAY_REFS: pos = lb->ref_pos; break; } /* look up whatever string this pos is for */ cptmp = map->reverse(pos); /* then lookup that string in items */ item_ptr = (struct itemlist *)item_hash.lookup(cptmp); /* do we have this string already? */ if (item_ptr == NULL) { /* not seen it, make a new slot */ item_ptr = &subitems[item_used]; item_hash.insert(cptmp, item_ptr); item_ptr->item = pos; /* store the ip position in the itemlist too, just ** in case we have to look it up in show_map_line */ item_ptr->ip_item = lb->ip_pos; item_ptr->last = now; item_ptr->first = lb->time; ++item_used; } /* we have the string in items now, so update stats in array */ ++(item_ptr->reqcount); item_ptr->bytecount += lb->bytes; /* we wish to count up how many times a given retcode * has occurred for this item */ t = (int)(lb->retcode/100); item_ptr->r_codes[t].reqcount++; item_ptr->r_codes[t].bytecount += lb->bytes; if (lb->time < item_ptr->first) item_ptr->first = lb->time; } /* no further use for this lookup hash */ item_hash.destroy(); /* calculate some stats; timespan, kbps, rps */ for(x = 0 ; x < item_used ; ++x) { item_ptr = &subitems[x]; item_ptr->timespan = item_ptr->last - item_ptr->first; if (item_ptr->timespan == 0) item_ptr->timespan = 1; /* hack to avoid /0 */ for(xx = 2 ; xx < 6 ; xx++) { item_ptr->r_codes[xx].rps = ((float) item_ptr->r_codes[xx].reqcount/item_ptr->timespan); item_ptr->r_codes[xx].bps = ((float) item_ptr->r_codes[xx].bytecount/item_ptr->timespan); } item_ptr->kbps = ((float) (item_ptr->bytecount/1024)/item_ptr->timespan); item_ptr->rps = ((float) item_ptr->reqcount/item_ptr->timespan); } /* top $X items; sort the array for display */ if (item_used) shellsort_wrapper(subitems, item_used, scf); //clrtobot(); /* display something */ /* a suitable header, at offset * (-1 because curses starts at zero, but I've started at 1) */ switch(display_mode_override) { case DISPLAY_URLS: mvaddstr(LINES_RESERVED+offset-1, 23, "URL"); break; case DISPLAY_HOSTS: mvaddstr(LINES_RESERVED+offset-1, 23, "HOST"); break; case DISPLAY_REFS: mvaddstr(LINES_RESERVED+offset-1, 23, "REFERRER"); break; } disp = offset; /* count how many we've shown */ for(x = 0, item_ptr = &subitems[0] ; x < items_size && disp < limit+offset ; ++item_ptr, ++x) { /* skip empty tablespaces, even though none should exist */ if (item_ptr->reqcount == 0) continue; /* render the line itself at position disp. map should be * already set from earlier in this function */ show_map_line(item_ptr, disp, map, 2, scf.numbers_mode); ++disp; } free(subitems); } /* }}} */ void translate_screen_to_pos() /* {{{ */ { /* don't do anything if there's nothing on screen */ if (items == NULL) return; /* convert cf.selected_item_screen to cf.selected_item_pos */ cf.selected_item_pos = items[cf.selected_item_screen].item; // /* cf.selected_item_pos may well be zero */ // if (cf.selected_item_pos) // { /* make a hash of it */ cf.selected_item_hash = TTHash(last_display_map->reverse(cf.selected_item_pos)); cf.selected_item_mode = cf.display_mode; // } } /* }}} */ void drawMarker(void) /* update position of asterisk next to URLs {{{ */ { if (cf.current_display_size == 0) return; /* ensure our marker isn't beyond the end of the list */ if (cf.selected_item_screen > cf.current_display_size-1) cf.selected_item_screen = cf.current_display_size-1; /* or above the start of it */ if (cf.selected_item_screen < 0) cf.selected_item_screen = 0; /* draw an asterisk next to the selected item and clear adjacent lines*/ mvaddch(LINES_RESERVED+cf.selected_item_screen-1, COLS_RESERVED-3, ' '); mvaddch(LINES_RESERVED+cf.selected_item_screen, COLS_RESERVED-3, '*' | A_BOLD); mvaddch(LINES_RESERVED+cf.selected_item_screen+1, COLS_RESERVED-3, ' '); /* come to rest under header */ move(SUBMENU_LINE_NUMBER, 0); refresh(); } /* }}} */ /* render contents of item_ptr into a statistics line onscreen, * at offset vert_location; pull the text portion out of m */ void show_map_line(struct itemlist *item_ptr, int vert_location, /* {{{ */ map *m, unsigned short indent, unsigned short number_mode) { if (number_mode == NUMBERS_HITS_BYTES) { /* start drawing at LINES_RESERVED */ mvprintw(LINES_RESERVED+vert_location, 0, "%5.0f %5.*f %5.*f %4.*f", item_ptr->reqcount, /* set 1dp if rps > 99, or 2 if not */ ((item_ptr->rps > 99) ? 1 : 2), item_ptr->rps, /* scale KB display; if >1000K lose the decimal point */ ((item_ptr->bytecount > (999*1024)) ? 0 : 1), ((float)item_ptr->bytecount/1024), /* scale KB/s display; if >1000K/s lose the decimal point */ ((item_ptr->kbps > 99) ? 0 : 1), item_ptr->kbps); } else /* number_mode == NUMBERS_RETCODES */ { mvprintw(LINES_RESERVED+vert_location, 0, "%5.0f %5.0f %5.0f %4.0f", item_ptr->r_codes[2].reqcount, item_ptr->r_codes[3].reqcount, item_ptr->r_codes[4].reqcount, item_ptr->r_codes[5].reqcount); } /* text after the number columns */ /* if we are displaying host/ip rather than anything else, we ** have slightly more work to do; we need to generate a line of the ** format host* [ip] ** * only needs to be present if it's still resolving. ** ip only needs to be present if list_show_ip is true. */ /* 23+indent = start at 23, but move to the right by indent ** spaces if we are doing, for example, a sublist */ if (m == hm) { /* we are displaying a host line */ char *h = NULL, *i = NULL; int host_item, ip_item; /* 200 chars ought to be enough? */ #define MAX_IP_STR_WIDTH 200 char str[MAX_IP_STR_WIDTH]; host_item = item_ptr->item; ip_item = item_ptr->ip_item; if (host_item >= 0) h = m->reverse(host_item); if (ip_item >= 0) i = im->reverse(ip_item); if (h && i) snprintf(str, MAX_IP_STR_WIDTH, "%s [%s]", h, i); else if (h) snprintf(str, MAX_IP_STR_WIDTH, "%s", h); else if (i) snprintf(str, MAX_IP_STR_WIDTH, "[%s]", i); else return; /* shouldn't get reached */ mvprintw(LINES_RESERVED+vert_location, 23+indent, "%.*s", (COLS-COLS_RESERVED /* width */), str); } else { char *str = m->reverse(item_ptr->item); mvprintw(LINES_RESERVED+vert_location, 23+indent, "%.*s", (COLS-COLS_RESERVED /* width */), str); } } /* }}} */ void shellsort_wrapper(struct itemlist *items, unsigned int size, /* {{{ */ struct config pcf) { short sort_method; if (size == 0) return; /* what are we displaying in the numbers column? */ switch(pcf.numbers_mode) { /* sorting by hits or bytes */ case NUMBERS_HITS_BYTES: sort_method = pcf.sort; break; /* sorting by a return code */ case NUMBERS_RETCODES: sort_method = pcf.retcodes_sort; break; } shellsort(items, size, sort_method); } /* }}} */ void shellsort(struct itemlist *items, unsigned int size, int sorttype) /* {{{ */ { int x; bool done; unsigned int i, j, jmp = size; double i_c, j_c; struct itemlist tmp; while (jmp > 1) { jmp >>= 1; do { done = true; for (j = 0; j < (size-jmp); j++) { i = j + jmp; if (cf.numbers_mode == NUMBERS_HITS_BYTES) { /* numbers_mode = NUMBERS_HITS_BYTES */ switch(sorttype) { default: case SORT_REQCOUNT: i_c = items[i].reqcount; j_c = items[j].reqcount; break; case SORT_REQPERSEC: i_c = items[i].rps; j_c = items[j].rps; break; case SORT_BYTECOUNT: i_c = items[i].bytecount; j_c = items[j].bytecount; break; case SORT_BYTESPERSEC: i_c = items[i].kbps; j_c = items[j].kbps; break; } } else { /* numbers_mode = NUMBERS_RETCODES */ /* this is easier ;) * see apachetop.h for explanation * of what SORTTYPE_OFFSET_HACK * is */ x = sorttype-SORTTYPE_OFFSET_HACK; i_c = items[i].r_codes[x].reqcount; j_c = items[j].r_codes[x].reqcount; } if (i_c > j_c) { #define P_S_S sizeof(struct itemlist) memcpy(&tmp, &items[i], P_S_S); memcpy(&items[i], &items[j], P_S_S); memcpy(&items[j], &tmp, P_S_S); done = false; } } } while (!done); } } /* }}} */ float readableNum(double num, char *suffix) /* {{{ */ { #define AP_TEN_KB ((double)(1024)*10) #define AP_TEN_MB ((double)(1024*1024)*10) #define AP_TEN_GB ((double)(1024*1024*1024)*10) if (num > AP_TEN_GB) { *suffix = 'G'; return (float)num/((double)(1024*1024*1024)); } if (num > AP_TEN_MB) { *suffix = 'M'; return (float)num/((double)(1024*1024)); } if (num > AP_TEN_KB) { *suffix = 'K'; return (float)num/1024; } *suffix = 'B'; return (float)num; } /* }}} */ void display_submenu_banner(const char *title, int title_len, const char *banner) { attron(A_REVERSE); mvaddstr(SUBMENU_LINE_NUMBER, 1, title); attroff(A_REVERSE); mvaddstr(SUBMENU_LINE_NUMBER, 1+title_len, banner); move(SUBMENU_LINE_NUMBER, 0); refresh(); } void clear_submenu_banner(void) { /* remove submenu banner */ move(SUBMENU_LINE_NUMBER, 0); clrtoeol(); refresh(); } void display_help(void) { clear(); move(0, 0); printw("ApacheTop version %s, Copyright (c) 2003-2004, Chris Elsworth", PACKAGE_VERSION); move(2, 0); addstr("ONE-TOUCH COMMANDS\n"); addstr("d : switch item display between urls/referrers/hosts\n"); addstr("n : switch numbers display between hits & bytes or return codes\n"); addstr("h or ? : this help window\n"); addstr("p : (un)pause display (freeze updates)\n"); addstr("q : quit ApacheTop\n"); addstr("up/down : move marker asterisk up/down\n"); addstr("right/left : enter/exit detailed subdisplay mode\n"); addstr("\n"); addstr("SUBMENUS:\n"); addstr("s: SORT BY: [the appropriate menu will appear for your display]\n"); addstr("\tr) requests R) reqs/sec b) bytes B) bytes/sec\n"); addstr("\t2) 2xx 3) 3xx 4) 4xx 5) 5xx\n\n"); addstr("t: TOGGLE SUBDISPLAYS ON/OFF:\n"); addstr("\tu) urls r) referrers h) hosts\n\n"); addstr("f: MANIPULATE FILTERS:\n"); addstr("\ta) add/edit menu c) clear all s) show active (not done yet)\n"); addstr("\ta: ADD FILTER SUBMENU\n"); addstr("\t\tu) to urls r) to referrers h) to hosts\n"); addstr("\n"); attron(A_REVERSE); addstr("Hit any key to continue:"); attroff(A_REVERSE); refresh(); cf.display_paused = true; } void display_active_filters() { clear(); move(0, 0); addstr("ApacheTop: Currently Active Filters\n"); addstr("\n"); attron(A_REVERSE); addstr("Hit any key to continue:"); attroff(A_REVERSE); refresh(); cf.display_paused = true; } void display_histogram() { int hist_height, hist_width; int i, j, age, oldest; //int y_scale; float y_scale, y_decr; float x_scale, max_bar = 0; char horiz_line[128]; struct logbits *lb; /* histogram starts at LINES_RESERVED+10 */ #define HISTOGRAM_START LINES_RESERVED+3 hist_height = 10; hist_width = 60; /* for every width character, figure out how many * characters high to draw the barchart */ float bar_height[hist_width]; char line[hist_width + 1]; for(i = 0 ; i < hist_width ; i++) bar_height[i] = 0; /* figure out scales; includes divide-by-zero avoidance hack */ if (cf.circle_mode == TIMED_CIRCLE) /* timed_circle = we know exactly how old we're going to get */ x_scale = ((float)hist_width/cf.circle_size); else x_scale = ((float)hist_width/getMAX(now - c->oldest(), 1)); /* don't scale when we have less data than we have room for */ if (x_scale > 1) x_scale = 1; while(c->walk(&lb) != -1) { if (!lb) continue; /* we have hist_width bars, and we need to put the entire * circle into that many bars. Devise which bar we're using * for this particular lb->time */ age = int( x_scale * (now - lb->time) ); /* add on x_scale; this is because if we are displaying 2 * seconds worth of data in one line, we only want to add on * half. */ bar_height[age] += x_scale; } /* find the maximum bar height we have. */ for(i = 0 ; i < hist_width ; ++i) max_bar = getMAX(max_bar, bar_height[i]); y_scale = max_bar; y_decr = ((float)y_scale / hist_height); for(i = 0 ; i < hist_height ; ++i) { if (i % 2 == 0) mvprintw(HISTOGRAM_START + i, 0, "%3.0f", y_scale); mvaddch(HISTOGRAM_START + i, 3, '|'); /* compose a row of hashes */ memset(line, ' ', hist_width); line[hist_width] = '\0'; for(j = 0 ; j < hist_width ; ++j) { if (bar_height[j] > y_scale) line[j] = '#'; } mvprintw(HISTOGRAM_START + i, 4, "%s", line); y_scale -= y_decr; } memset(horiz_line, '-', hist_width); horiz_line[hist_width] = '\0'; mvprintw(HISTOGRAM_START + hist_height, 2, "0+%*s", hist_width, horiz_line); mvprintw(HISTOGRAM_START + hist_height+1, 4, "NOW"); mvprintw(HISTOGRAM_START + hist_height+1, hist_width+3, "-%ds", now - c->oldest()); refresh(); } apachetop-0.19.7/src/display.h000066400000000000000000000027111351455032200161620ustar00rootroot00000000000000#ifndef _DISPLAY_H_ #define _DISPLAY_H_ /* macro to render "paused" in inverse at the coords given. This is * called from apachetop.cc/read_key() when pause is activated, and in each * display.cc/draw_header() when pause is turned on. */ #define DRAW_PAUSED(x,y) attron(A_REVERSE); \ mvaddstr(x, y, "paused"); \ attroff(A_REVERSE) #define SUBMENU_LINE_NUMBER LINES_RESERVED-2 /* display() makes an array of these, sorts, and displays */ struct itemlist { unsigned int hash; int item, ip_item; double reqcount; double bytecount; time_t first, last, timespan; float rps, kbps; struct hitinfo r_codes[6]; }; bool display(time_t last_display); void display_header(); void display_list(); void display_sub_list(short display_mode_override, unsigned short offset, unsigned short limit); void translate_screen_to_pos(); void drawMarker(void); #define NO_INDENT 0 void show_map_line(struct itemlist *item_ptr, int vert_location, map *m, unsigned short indent, unsigned short number_mode); void shellsort_wrapper(struct itemlist *items, unsigned int size, struct config pcf); void shellsort(struct itemlist *items, unsigned int size, int sorttype); float readableNum(double num, char *suffix); void display_submenu_banner(const char *title, int title_len, const char *banner); void clear_submenu_banner(void); void display_help(void); void display_active_filters(void); void display_histogram(); #endif apachetop-0.19.7/src/filters.cc000066400000000000000000000013741351455032200163270ustar00rootroot00000000000000#include "apachetop.h" Filter::Filter(void) { #if HAVE_PCRE_H_ regexp = NULL; #endif filter_text = NULL; } Filter::~Filter(void) { this->empty(); } void Filter::store(const char *filter) { if (filter_text) free(filter_text); filter_text = strdup(filter); #if HAVE_PCRE_H_ if (regexp) delete regexp; regexp = new RegEx(filter); #endif return; } bool Filter::isactive() { return filter_text ? true : false; } bool Filter::match(const char *string) { if (!filter_text) return false; #if HAVE_PCRE_H_ return regexp->Search(string); #else return strstr(string, filter_text); #endif } void Filter::empty(void) { #if HAVE_PCRE_H_ if (regexp) delete regexp; regexp = NULL; #endif if (filter_text) free(filter_text); filter_text = NULL; } apachetop-0.19.7/src/filters.h000066400000000000000000000011311351455032200161600ustar00rootroot00000000000000#ifndef _FILTERS_H_ #define _FILTERS_H_ /* Filter class ** ** Each instance has one filter, which is either plaintext or regular ** expression (if HAVE_PCRE_H_ is defined): Quick example: ** ** f = new Filter(); ** f->store("(movies|music)"); ** if (f->isactive() && f->match("some string with movies in it")) ** // you got a match */ class Filter { public: Filter(void); ~Filter(void); void store(const char *filter); bool isactive(void); bool match(const char *string); void empty(void); private: char *filter_text; #if HAVE_PCRE_H RegEx *regexp; #endif }; #endif /* _FILTERS_H_ */ apachetop-0.19.7/src/hits_circle.cc000066400000000000000000000053501351455032200171450ustar00rootroot00000000000000/* class to encapsulate set of functions to manage a circular array; * recent hit information is stored in the looping array; each hit * has information written into the next slot of structs. If * we reach the end, start over. Then the top-URL/IP is summed up from * this information. The bigger the table, the more hits you'll have * to summarise from. */ #include "apachetop.h" extern map *hm, *um, *rm; int Hits_Circle::create(unsigned int passed_size) { size = passed_size; pos = 0; walkpos = 0; tab = (circle_struct *) calloc(size, sizeof( circle_struct)); if (!tab) { abort(); } reqcount = bytecount = 0; memset(rc_summary, 0, sizeof(rc_summary)); return 0; } int Hits_Circle::insert(struct logbits lb) { circle_struct *posptr; short rc_tmp_old, rc_tmp_new; /* insert the given data into the current position, * and update pos to point at next position */ posptr = &tab[pos]; if (posptr->time == 0) /* if this is a new insert, increment our count */ ++reqcount; else { /* if this is re-using an old slot, remove refcount for the * previous data before we vape it */ hm->sub_ref(posptr->host_pos); um->sub_ref(posptr->url_pos); rm->sub_ref(posptr->ref_pos); } /* maintain some stats */ /* bytecount; remove the previous one and add the new one */ bytecount -= posptr->bytes; bytecount += lb.bytes; /* retcodes, remember how many we have of each */ rc_tmp_old = (int)posptr->retcode/100; rc_tmp_new = (int)lb.retcode/100; if (rc_tmp_old != rc_tmp_new) { --rc_summary[rc_tmp_old]; ++rc_summary[rc_tmp_new]; } /* store the data */ memcpy(posptr, &lb, sizeof(lb)); ++pos; /* see if we're running out of space. We'd like to keep however many * hits of data the user has asked for; if we're not managing * that, increase */ if (pos == size) { /* loop round */ pos = 0; } return 0; } //int Hits_Circle::walk(unsigned int *url_pos, unsigned int *ip_pos, // int *bytes, time_t *time, unsigned int *ipl, unsigned int *retcode) int Hits_Circle::walk(struct logbits **lb) { /* return each value in the circle one by one, starting at 0 and * working up; return 0 when there are more to go, or -1 when we're * done */ *lb = NULL; if (walkpos == size || tab[walkpos].time == 0) { walkpos = 0; return -1; } *lb = &tab[walkpos]; ++walkpos; return 0; } time_t Hits_Circle::oldest(void) { int tmp; /* return the first entry we have. normally this will be pos+1, but * cater for circumstances where it is isn't; ie we're initially * filling up the array (use 0), or we're at position size (use 0) */ if (pos == size) tmp = 0; /* earliest will be 0 */ else tmp = pos + 1; /* earliest is next element */ if (tab[tmp].time > 0) return tab[tmp].time; return tab[0].time; } apachetop-0.19.7/src/hits_circle.h000066400000000000000000000012151351455032200170030ustar00rootroot00000000000000#ifndef _HITS_CIRCLE_H_ #define _HITS_CIRCLE_H_ class Hits_Circle : public Circle { public: int create(unsigned int passed_size); int insert(struct logbits lb); int walk(struct logbits **lb); void updatestats(void) {} time_t oldest(void); double getreqcount(void) { return reqcount; } double getbytecount(void) { return bytecount; } double getsummary(int r_c) { return rc_summary[r_c]; } private: int resize(int newsize); double reqcount, bytecount; double rc_summary[6]; typedef struct logbits circle_struct; circle_struct *tab; int size; /* total size of circle table */ int pos; /* where are we now? */ int walkpos; }; #endif apachetop-0.19.7/src/inlines.cc000066400000000000000000000015271351455032200163200ustar00rootroot00000000000000#include "apachetop.h" #define THREE_QUARTERS 24 #define ONE_EIGHTH 4 #define HIGH_BITS (~((unsigned int)(~0) >> ONE_EIGHTH)) inline unsigned int StringHash(register const char *str) { register unsigned int val; register unsigned int i; for (val = 0; *str; str++) { val = (val << ONE_EIGHTH) + *str; if ((i = val & HIGH_BITS) != 0) val = (val ^ (i >> THREE_QUARTERS)) & ~HIGH_BITS; } return val; } inline unsigned int QuickHash(register const char *str) { register unsigned int val, tmp; for(val = 0 ; *str ; str++) { val = (val << 4) + *str; if ((tmp = (val & 0xf0000000))) val = (val ^ (tmp >> 24)) ^ tmp; } return val; } inline unsigned long TTHash(register const char *str) { unsigned long hash = 5381; int c; while ((c = *str++)) hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ return hash; } apachetop-0.19.7/src/log.cc000066400000000000000000000220201351455032200154270ustar00rootroot00000000000000#include "apachetop.h" #include "inlines.cc" #define RESOLVING_STRING (char *)"..." #define NO_RESOLVED_INFO "?" extern map *um, /* urlmap */ *im, /* ipmap */ *hm, /* hostmap */ *rm, /* referrermap */ *fm; /* filemap */ extern time_t now; extern config cf; extern Circle *c; extern Queue want_host, want_ip; #if HAVE_ADNS_H extern adns_state adns; #endif /* CommonLogParser handles common and combined, despite its name */ int CommonLogParser::parse(char *logline, struct logbits *b) { char *bufsp, *bufcp, *ptr; char *workptr; struct sockaddr_in addr; bufsp = logline; /* host first */ bufcp = strchr(logline, ' '); if (!bufcp) return -1; *bufcp = '\0'; ++bufcp; /* quickly figure out if this is an IP or a host. We do this by * checking each character of it; if every character is either a * digit or a dot, then it's an IP (no host can just be digits) */ for(workptr = bufsp ; *workptr ; workptr++) { if (isdigit(*workptr)) continue; if (*workptr == '.') continue; /* it's neither a digit or a dot */ break; } ptr = bufsp; if (*workptr) { /* it is a hostname */ /* insert will return existing position if it exists */ b->host_pos = hm->insert(ptr); b->host_hash = TTHash(ptr); b->want_host = false; /* cos we have it */ #if HAVE_ADNS_H if (cf.do_resolving) { b->want_ip = true; dprintf("lookup %s\n", ptr); /* fire off a query with adns */ b->dns_query = new adns_query; adns_submit(adns, ptr, adns_r_a, (adns_queryflags) NULL, NULL, b->dns_query); b->ip_pos = im->insert(RESOLVING_STRING); b->ip_hash = TTHash(RESOLVING_STRING); } else #endif /* HAVE_ADNS_H */ { /* don't resolve the IP, and use -1 which means * "there is nothing of interest here" */ b->ip_pos = -1; b->want_ip = false; } } else { /* it is an IP */ b->ip_pos = im->insert(ptr); b->ip_hash = TTHash(ptr); b->want_ip = false; /* we have the IP already */ #if HAVE_ADNS_H if (cf.do_resolving) { /* this is so we'll get a display like ..resolving.. [212.13.201.101] then once resolved: clueful.shagged.org [212.13.201.101] */ b->host_pos = hm->insert(RESOLVING_STRING); b->host_hash = TTHash(RESOLVING_STRING); b->want_host = true; /* we're going to get this */ /* construct network byte order num ** for adns_submit_reverse */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(ptr); b->dns_query = new adns_query; adns_submit_reverse(adns, (struct sockaddr *)&addr, adns_r_ptr, (adns_queryflags)adns_qf_owner, NULL, b->dns_query); } else #endif /* HAVE_ADNS_H */ { /* don't resolve the host, use the IP */ b->host_pos = hm->insert(ptr); b->host_hash = TTHash(ptr); b->want_host = false; /* we are not resolving */ } } /* now skip to date */ if (!(bufcp = strchr(bufcp, '['))) return -1; bufcp++; b->time = now; /* be lazy */ /* find the end of the date */ if (!(bufcp = strchr(bufcp, ']'))) return -1; bufcp += 3; /* from end of date to first char of method */ /* URL. processURL() will update bufcp to point at the end so we can * continue processing from there */ if ((ptr = this->processURL(&bufcp)) == NULL) return -1; /* get url_pos for this url; for circle_struct (c) later */ b->url_pos = um->insert(ptr); b->url_hash = TTHash(ptr); /* return code */ b->retcode = atoi(bufcp); bufcp += 4; /* bytecount */ b->bytes = atoi(bufcp); /* this may be the end of the line if it's a common log; if * it's combined then we have referrer and user agent left */ if (!(bufsp = strchr(bufcp, '"'))) { /* nothing left, its common */ /* fill in a dummy value for referrer map */ b->ref_pos = rm->insert((char *)"Unknown"); return 0; } bufsp += 1; /* skip to first character of referrer */ /* find the end of referrer and null it */ if (!(bufcp = strchr(bufsp, '"'))) return -1; *bufcp = '\0'; /* unless they want to keep it, skip over the protocol, ie http:// */ if ((cf.preserve_ref_protocol == 0) && (bufcp = strstr(bufsp, "://"))) bufsp = bufcp + 3; /* we could munge the referrer now; cut down the path elements, * remove querystring, but we'll leave that for a later date */ // b->referrer = bufsp; /* get ref_pos for this url; for circle_struct (c) later */ b->ref_pos = rm->insert(bufsp); b->ref_hash = TTHash(bufsp); /* user-agent is as yet unused */ return 0; } int AtopLogParser::parse(char *logline, struct logbits *b) { return 0; } /* generic parser helper functions */ char *LogParser::processURL(char **buf) /* {{{ */ { char *bufcp, *realstart, *endptr; int length; bufcp = *buf; /* this skips past the method */ if (!(bufcp = strchr(bufcp, ' ')) ) return NULL; ++bufcp; // skip space realstart = bufcp; /* find the end of url; locate a protocol, out of the following list */ if ( !(endptr = strstr(bufcp, " HTTP/")) #if WITH_REAL_PROTOCOLS /* v0.12: RealServer logs are very similar to Apache's, * so we can support those too! Cool! */ && !(endptr = strstr(bufcp, " RTSP/")) /* RealStreaming UDP */ && !(endptr = strstr(bufcp, " RTSPT/")) /* RealStreaming TCP */ && !(endptr = strstr(bufcp, " RTSPH/")) /* RealStreaming HTTP */ #endif ) return NULL; /* null the space in front of it */ *endptr = '\0'; /* TODO maybe we can use the protocol someday.. */ /* this is all mungeURL is interested in */ length = endptr - realstart; /* now find the finishing ", so parse* can deal with rest of line */ if (!(endptr = strstr(endptr+1, "\" "))) return NULL; mungeURL(&realstart, &length); /* feed back where the end of the URL is */ *buf = endptr+2; return realstart; } /* }}} */ /* munge the url passed in *url inplace; * *length is the original length, and we update it once we're done */ int LogParser::mungeURL(char **url, int *length) /* {{{ */ { int skipped = 0; char *bufcp, *endptr, *workptr; endptr = *url + *length; *endptr = '\0'; /* do we want to keep the query string? */ if (!cf.keep_querystring) { /* null the first ? or & - anything after * it is unrequired; it's the querystring */ if ((workptr = strchr(*url, '?')) || (workptr = strchr(*url, '&')) ) { /* we might have overrun the end of the real URL and * gone into referrer or something. Check that. */ if (workptr < endptr) { /* we're ok */ *workptr = '\0'; bufcp = workptr+1; } } } /* how many path segments of the url are we keeping? */ if (cf.keep_segments > 0) { /* given a path of /foo/bar/moo/ and a keep_segments of 2, * we want the / after the second element */ bufcp = workptr = *url + 1; /* skip leading / */ //dprintf("workptr is %s\n", workptr); /* now skip the next keep_segments slashes */ while (skipped < cf.keep_segments && workptr < endptr) { workptr++; if (*workptr == '/') { /* discovered a slash */ skipped++; /* bufcp becomes the char after / */ bufcp = workptr+1; } /* if we hit the end before finding the right number * of slashes, we just keep it all */ if (workptr == endptr) bufcp = workptr; } *bufcp = '\0'; } /* do we want to lowercase it all? */ if (cf.lowercase_urls) { workptr = *url; while(workptr < endptr) { *workptr = tolower(*workptr); workptr++; } } /* fin */ return 0; } /* }}} */ #if HAVE_ADNS_H /* adns; check to see if any queries have returned, and populate the circle * as required. Be careful of any circle entries that have expired since * the query was started. */ void collect_dns_responses() { int err; struct logbits *lb; adns_answer *answer; int got_host = false, got_ip = false; /* check every circle entry that has want_host or want_ip */ while(c->walk(&lb) != -1) { if (lb->want_host == false && lb->want_ip == false) continue; // dprintf("adns_check for %p\n", lb); /* this circle slot has an outstanding query */ err = adns_check(adns, lb->dns_query, &answer, NULL); if (err == EAGAIN) { /* still waiting */ continue; } /* some form of reply. Be it success or error, this query is * now done. */ got_host = lb->want_host; got_ip = lb->want_ip; lb->want_host = false; lb->want_ip = false; delete lb->dns_query; if (answer->status == adns_s_ok) { /* we have a reply */ // dprintf("got a reply\n"); if (got_host) { /* we'll have this new host in the hostmap ta */ lb->host_pos = hm->insert(*answer->rrs.str); lb->host_hash = TTHash(*answer->rrs.str); } else if (got_ip) { /* put the IP into the ipmap */ lb->ip_pos = im->insert(inet_ntoa(*answer->rrs.inaddr)); lb->ip_hash = TTHash(inet_ntoa(*answer->rrs.inaddr)); } free(answer); continue; } /* assume this IP has no reverse info; so we'll put the IP * into Host as well; this is so that the Host list will be * maintained properly (if we just put ? into Host, then * they bunch up together) */ lb->host_pos = hm->insert(im->reverse(lb->ip_pos)); lb->host_hash = TTHash(im->reverse(lb->ip_pos)); free(answer); continue; } } #endif /* HAVE_ADNS_H */ apachetop-0.19.7/src/log.h000066400000000000000000000034121351455032200152750ustar00rootroot00000000000000#ifndef _LOG_H_ #define _LOG_H_ /* types */ #define LOG_COMMON 1 #define LOG_COMBINED 2 #define LOG_ATOP 3 /* parse*() fills in this struct */ struct logbits { int url_pos; /* location of url in urlmap */ int url_hash; /* hash of url string */ /* host and ip. Whatever we have in the log gets set here initially, ** then want_foo is set to false. want_otherfoo is set to true ** explicitly; then an entry is made into the relevant Queue ** (want_host or want_ip). adns does the resolving, and then ** whatever it finds is placed in otherfoo, and want_otherfoo is set ** to false */ bool want_host; int host_pos; /* location of host in hostmap */ int host_hash; /* hash of host string */ bool want_ip; //unsigned int ipl; /* numerical representation of IP */ int ip_pos; /* location of IP in ipmap */ int ip_hash; /* hash of IP string */ #if HAVE_ADNS_H adns_query *dns_query; //Resolver dns_query; #endif int ref_pos; /* location of referrer in map */ int ref_hash; /* hash of Referrer string */ int fileid; /* which file descriptor we're from */ int file_pos; /* location of file in filemap */ unsigned short retcode; /* return code */ unsigned int bytes; /* body of result page */ time_t time; /* time of request, unixtime */ }; class LogParser { public: virtual int parse(char *logline, struct logbits *b) = 0; char *processURL(char **buf); int mungeURL(char **url, int *length); }; class CommonLogParser : public LogParser { int parse(char *logline, struct logbits *b); }; class AtopLogParser : public LogParser { int parse(char *logline, struct logbits *b); }; void collect_dns_responses(); #endif apachetop-0.19.7/src/map.cc000066400000000000000000000072171351455032200154360ustar00rootroot00000000000000#include "apachetop.h" /* string to hash map, and vice versa; for quick string lookups */ extern Circle *c; extern time_t now; int map::create(int passed_size) { size = passed_size; tab = (struct hash_struct *)malloc(size * sizeof(struct hash_struct)); /* initialise map to be empty */ this->empty(0, size); /* a hash for quick string lookups */ tab_hash = new OAHash; tab_hash->create(size*9); return 0; } void map::empty(int from, int to) { int x; struct hash_struct *ptr; for(ptr = &tab[from], x = from; x < to; ++ptr, ++x) { ptr->refcount = 0; ptr->pos = x; ptr->string = NULL; ptr->time = 0; } return; } int map::destroy(void) { free(tab); tab_hash->destroy(); delete tab_hash; return 0; } int map::resize(int newsize) { int x; struct hash_struct *newtab; /* we have to resize the tab, then clear the hash, remake the hash * at the newsize*5, then re-populate the hash. Remaking the hash is * quite expensive, so we'd like to do this as infrequently as * possible */ newtab = (struct hash_struct *) realloc(tab, newsize * sizeof(struct hash_struct)); /* did it work? */ if (!newtab) { perror("realloc map"); exit(1); } tab = newtab; /* empty the new area */ this->empty(size, newsize); /* remake the hash since memory addresses may have moved */ delete tab_hash; tab_hash = new OAHash; tab_hash->create(newsize*9); for (x = 0 ; x < size ; ++x) { tab_hash->insert(tab[x].string, &tab[x]); } // dprintf("%p resize from %d to %d\n", this, size, newsize); size = newsize; return 0; } int map::insert(char *string) { int x; struct hash_struct *ptr; /* if this string is in the map, return existing position only */ if ((x = this->lookup(string)) != -1) { /* we wanted to insert, but didn't, so the refcount for this * particular entry is incremented */ tab[x].refcount++; // dprintf("%d Found %p %d for %s\n", time(NULL), this, x, string); return x; } /* find free table slot FIXME make this more efficient */ for (ptr = &tab[0], x = 0; x <= size; ++ptr, ++x) { if (x == size) { /* none free, make room */ this->resize(size * 2); /* realloc may well move the table in * memory so update the pointer */ ptr = &tab[x]; break; } /* if this map entry has a refcount of zero (this is new in * v0.12) then nothing is pointing at it, hopefully, so it * can be nuked */ if (ptr->refcount == 0) break; } /* if we are re-using, free up the old data */ if (ptr->string) { tab_hash->remove(ptr->string); free(ptr->string); } // dprintf("%d Using %p %d for %s\n", time(NULL), this, x, string); /* make entry in our table */ ptr->time = now; ptr->refcount = 1; ptr->string = strdup(string); /* add into hash */ tab_hash->insert(ptr->string, ptr); return x; } int map::remove(char *string) { struct hash_struct *ptr; /* find string in hash */ if ((ptr = (struct hash_struct *)tab_hash->lookup(string))) { // dprintf("%d Remove %p %d for %s\n", time(NULL), this, ptr->pos, string); /* remove from table */ free(ptr->string); ptr->string = NULL; ptr->time = 0; ptr->refcount = 0; /* remove from hash */ tab_hash->remove(string); } return 0; } int map::lookup(char *string) { struct hash_struct *ptr; if ((ptr = (struct hash_struct *)tab_hash->lookup(string))) { ptr->time = now; /* touch it, so insert won't remove */ return ptr->pos; } return -1; } char *map::reverse(int pos) { /* return pointer to char for this string pos */ return tab[pos].string; } void map::sub_ref(int pos) { // dprintf("%d subref %p %d for %s\n", // time(NULL), this, pos, tab[pos].string); if (tab[pos].refcount > 0) tab[pos].refcount--; } apachetop-0.19.7/src/map.h000066400000000000000000000007221351455032200152720ustar00rootroot00000000000000#ifndef _MAP_H_ #define _MAP_H_ class map { public: int create(int passed_size); void empty(int from, int to); int destroy(void); int resize(int newsize); int insert(char *string); int remove(char *string); int lookup(char *string); char *reverse(int pos); void sub_ref(int pos); private: struct hash_struct { unsigned int refcount; int pos; char *string; time_t time; }; struct hash_struct *tab; int size; OAHash *tab_hash; }; #endif apachetop-0.19.7/src/ohtbl.cc000066400000000000000000000046131351455032200157660ustar00rootroot00000000000000#include "apachetop.h" #include "inlines.cc" #define OACMP(a, b) (strcmp((char *)a, (char *)b) == 0) ? 1 : 0 #define OA_H1(k) (TTHash(k) % positions) #define OA_H2(k) (1 + (StringHash(k) % (positions-2))) #define OA_HASH(k) (OA_H1(k) + (i * OA_H2(k))) % positions static int primes[] = {101, 241, 499, 1009, 2003, 3001, 4001, 5003, 10007, 15013, 19381, 29131, 38011, 45589, 80021, 100003, 150001, 200003, 500009, 5000011, 0}; int OAHash::getNextPrime(int size) { register int *prime; for (prime = &primes[0] ; *prime ; prime++) if (*prime > size) return *prime; return size*2; /* we're out of primes so give up */ } int OAHash::create(int p_positions) { unsigned int i; /* use the next prime up from p_positions */ if ((p_positions = getNextPrime(p_positions)) == -1) abort(); if ((table = (ohEntry *)malloc(p_positions * sizeof(ohEntry))) == NULL) return -1; positions = p_positions; for(i = 0 ; i < positions ; ++i) table[i].key = NULL; size = 0; return 0; } void OAHash::destroy(void) { free(table); return; } void *OAHash::insert(char *key, void *data) { register unsigned int p, i; void *d; // Do not exceed the number of positions in the table. if (size == positions) return NULL; // Do nothing if the data is already in the table. if ((d = lookup(key))) /* return the position in case it can be used */ return d; for (i = 0; i < positions; ++i) { p = OA_HASH(key); if (table[p].key == NULL || table[p].key == &vacated) { // Insert the data into the table. table[p].key = key; table[p].data = data; size++; return data; } } return NULL; } int OAHash::remove(char *key) { register unsigned int p, i; for (i = 0; i < positions; ++i) { p = OA_HASH(key); if (table[p].key == NULL) { // Return that the data was not found. return -1; } else if (table[p].key == &vacated) { // Search beyond vacated positions. continue; } else if (OACMP(table[p].key, key)) { table[p].key = &vacated; size--; return 0; } } return -1; } void *OAHash::lookup(char *key) { register unsigned int p, i; for (i = 0; i < positions; ++i) { p = OA_HASH(key); if (table[p].key == NULL) { return NULL; } else if (table[p].key == &vacated) { continue; } else if (OACMP(table[p].key, key)) // return pointer to data return table[p].data; } return NULL; } apachetop-0.19.7/src/ohtbl.h000066400000000000000000000006561351455032200156330ustar00rootroot00000000000000#ifndef _OHTBL_H_ #define _OHTBL_H_ class OAHash { public: int create(int p_positions); void destroy(void); void *insert(char *key, void *data); int remove(char *key); void *lookup(char *key); unsigned int size; private: int getNextPrime(int size); int cmp(const void *a, const void *b); typedef struct { char *key; void *data; } ohEntry; unsigned int positions; ohEntry *table; char vacated; }; #endif apachetop-0.19.7/src/pcre_cpp_wrapper.h000066400000000000000000000131651351455032200200550ustar00rootroot00000000000000// // regex.hpp 1.0 Copyright (c) 2003 Peter Petersen (pp@on-time.de) // Simple C++ wrapper for PCRE // // This source file is freeware. You may use it for any purpose without // restriction except that the copyright notice as the top of this file as // well as this paragraph may not be removed or altered. // // This header file declares class RegEx, a simple and small API wrapper // for PCRE. // // RegEx::RegEx(const char * regex, int options = 0) // // The constructor's first parameter is the regular expression the // created object shall implement. Optional parameter options can be // any combination of PCRE options accepted by pcre_compile(). If // compiling the regular expression fails, an error message string is // thrown as an exception. // // RegEx::~RegEx() // // The destructor frees all resources held by the RegEx object. // // int RegEx::SubStrings(void) const // // Method SubStrings() returns the number of substrings defined by // the regular expression. The match of the entire expression is also // considered a substring, so the return value will always be >= 1. // // bool RegEx::Search(const char * subject, int len = -1, int options = 0) // // Method Search() applies the regular expression to parameter subject. // Optional parameter len can be used to pass the subject's length to // Search(). If not specified (or less than 0), strlen() is used // internally to determine the length. Parameter options can contain // any combination of options PCRE_ANCHORED, PCRE_NOTBOL, PCRE_NOTEOL. // PCRE_NOTEMPTY. Search() returns true if a match is found. // // bool RegEx::SearchAgain(int options = 0) // // SearchAgain() again applies the regular expression to parameter // subject last passed to a successful call of Search(). It returns // true if a further match is found. Subsequent calls to SearchAgain() // will find all matches in subject. Example: // // if (Pattern.Search(astring)) { // do { // printf("%s\n", Pattern.Match()); // } while (Pattern.SearchAgain()); // } // // Parameter options is interpreted as for method Search(). // // const char * RegEx::Match(int i = 1) // // Method Match() returns a pointer to the matched substring specified // with parameter i. Match() may only be called after a successful // call to Search() or SearchAgain() and applies to that last // Search()/SearchAgain() call. Parameter i must be less than // SubStrings(). Match(-1) returns the last searched subject. // Match(0) returns the match of the complete regular expression. // Match(1) returns $1, etc. // // The bottom of this file contains an example using class RegEx. It's // the simplest version of grep I could come with. You can compile it by // defining REGEX_DEMO on the compiler command line. // #ifndef _REGEX_H #define _REGEX_H #include #ifndef _PCRE_H #include "pcre.h" #endif class RegEx { public: ///////////////////////////////// RegEx(const char * regex, int options = 0) { const char * error; int erroffset; re = pcre_compile(regex, options, &error, &erroffset, NULL); if (re == NULL) throw error; pe = pcre_study(re, 0, &error); pcre_fullinfo(re, pe, PCRE_INFO_CAPTURECOUNT, &substrcount); substrcount++; ovector = new int[3*substrcount]; matchlist = NULL; }; ///////////////////////////////// ~RegEx() { ClearMatchList(); delete ovector; if (pe) pcre_free(pe); pcre_free(re); } ///////////////////////////////// inline int SubStrings(void) const { return substrcount; } ///////////////////////////////// bool Search(const char * subject, int len = -1, int options = 0) { ClearMatchList(); return pcre_exec(re, pe, lastsubject = subject, slen = (len >= 0) ? len : strlen(subject), 0, options, ovector, 3*substrcount) > 0; } ///////////////////////////////// bool SearchAgain(int options = 0) { ClearMatchList(); return pcre_exec(re, pe, lastsubject, slen, ovector[1], options, ovector, 3*substrcount) > 0; } ///////////////////////////////// const char * Match(int i = 1) { if (i < 0) return lastsubject; if (matchlist == NULL) pcre_get_substring_list(lastsubject, ovector, substrcount, &matchlist); return matchlist[i]; } private: inline void ClearMatchList(void) { if (matchlist) pcre_free_substring_list(matchlist), matchlist = NULL; } pcre * re; pcre_extra * pe; int substrcount; int * ovector; const char * lastsubject; int slen; const char * * matchlist; }; // Below is a little demo/test program using class RegEx #ifdef REGEX_DEMO #include #include "regex.hpp" /////////////////////////////////////// int main(int argc, char * argv[]) { if (argc != 2) { fprintf(stderr, "Usage: grep pattern\n\n" "Reads stdin, searches 'pattern', writes to stdout\n"); return 2; } try { RegEx Pattern(argv[1]); int count = 0; char buffer[1024]; while (fgets(buffer, sizeof(buffer), stdin)) if (Pattern.Search(buffer)) fputs(buffer, stdout), count++; return count == 0; } catch (const char * ErrorMsg) { fprintf(stderr, "error in regex '%s': %s\n", argv[1], ErrorMsg); return 2; } } #endif // REGEX_DEMO #endif // _REGEX_H apachetop-0.19.7/src/queue.cc000066400000000000000000000013171351455032200160000ustar00rootroot00000000000000#include "apachetop.h" Queue::Queue(void) { size = 0; head = NULL; tail = NULL; } Queue::~Queue(void) { delete head; } void Queue::push(void *q) { Node *temp; temp = new Node; temp->x = q; temp->next = NULL; if (size == 0) { head = temp; tail = temp; } else { tail->next = temp; tail = temp; } size++; } // remove and return first entry int Queue::pop(void **q) { Node *temp; if (!head) { *q = NULL; return -1; } // return the first entry *q = head->x; if (head->next) // we have another node to point at temp = head->next; else // no more nodes temp = NULL; delete head; head = temp; size--; return 0; // success } int Queue::entries(void) { return size; } apachetop-0.19.7/src/queue.h000066400000000000000000000004331351455032200156400ustar00rootroot00000000000000#ifndef _QUEUE_H_ #define _QUEUE_H_ class Node { public: void *x; Node *next; Node() { next = NULL; } }; class Queue { private: Node *head; Node *tail; int size; public: Queue(void); ~Queue(void); void push(void *r); int pop(void **r); int entries(void); }; #endif apachetop-0.19.7/src/resolver.cc000066400000000000000000000022331351455032200165130ustar00rootroot00000000000000/* ApacheTop resolver functions parse() will call add_request() this checks to see if we have an IP -> host mapping already if not it fires off adns_submit_reverse */ #if HAVE_ADNS_H #include "apachetop.h" Resolver::Resolver(void) { if (adns_init(&adns, adns_if_noenv, 0) != 0) { perror("adns_init"); exit(1); } } Resolver::~Resolver(void) { adns_finish(adns); } int Resolver::add_request(char *request, enum resolver_action act) { struct sockaddr_in addr; /* depending on resolver_action, this is an IP or a host to lookup * the other way */ /* at the moment we only do IPs */ switch(act) { case resolver_gethost: /* see if this IP is in host_ip_tablei; if it is we may be * able to get a host for it (or an adns is already looking * it up/has looked it up and got negative reply) */ addr.sin_family = AF_INET; /* add error checking */ #if HAVE_INET_ATON inet_aton(request, &(addr.sin_addr)); #else addr.sin_addr.s_addr = inet_addr(request); #endif // adns_submit_reverse(adns, (struct sockaddr *)&addr, adns_r_ptr, // (enum adns_queryflags)adns_qf_owner, NULL, b->dns_query); break; } } #endif /* HAVE_ADNS_H */ apachetop-0.19.7/src/resolver.h000066400000000000000000000005531351455032200163600ustar00rootroot00000000000000#ifndef _RESOLVER_H_ #define _RESOLVER_H_ enum resolver_action { resolver_getip = 0, resolver_gethost = 1 }; class Resolver { public: Resolver(void); ~Resolver(void); int add_request(char *request, enum resolver_action act); private: adns_state adns; struct host_ip_table { unsigned long ipl; char *host; }; OAHash *host_ip_hash; }; #endif apachetop-0.19.7/src/timed_circle.cc000066400000000000000000000161161351455032200173020ustar00rootroot00000000000000#include "apachetop.h" /* Timed_Circle class; stores all the hits in the last $X seconds. * * tab is a timed_circle_struct pointer, which we malloc to be $X; thus each * struct is one second. * * tab[x].hits is a hit_struct pointer, malloc'ed to roughly how many * hits we expect to see per second. This can be intelligently guessed from * past data where possible. Each struct within this is then one hit. */ #define MAX_BUCKET (bucketsize) extern time_t now; /* global ApacheTop-wide to save on time() calls */ extern struct gstat gstats; extern map *hm, *um, *rm; int Timed_Circle::create(unsigned int size) { /* increment size by 1, because one bucket is always being used for * a transitional second; thus could be empty. So we always have the * requested amount of data (or perhaps slightly more) */ size++; /* malloc the buckets */ tab = (struct timed_circle_struct *) malloc(size * sizeof(struct timed_circle_struct)); if (tab == NULL) return -1; this->initbuckets(0, size); bucketsize = size; bucketpos = 0; /* start at the beginning */ walk_hitpos = walk_bucketpos = 0; return 0; } int Timed_Circle::initbuckets(const unsigned int from, const unsigned int to) { unsigned int x; struct timed_circle_struct *ptr; /* clear some values to 0 */ for(x = from, ptr = &tab[x] ; x < to; ++x, ++ptr) { /* set up hits array for this bucket */ ptr->hitsize = 5; ptr->hits = (HIT *)malloc(sizeof(HIT) * ptr->hitsize); if (!ptr->hits) abort(); memset(ptr->hits, 0, sizeof(HIT) * ptr->hitsize); this->resetbucketstats(x); } return 0; } void Timed_Circle::resetbucketstats(const unsigned int r) { int x; HIT *hit; struct timed_circle_struct *bucket; //dprintf("resetting bucket stats %d\n", r); bucket = &tab[r]; /* reset statistics for this bucket */ bucket->time = now; bucket->bytecount = 0; bucket->reqcount = 0; bucket->rc_summary[2] = 0; bucket->rc_summary[3] = 0; bucket->rc_summary[4] = 0; bucket->rc_summary[5] = 0; /* free up any storage associated with the hit previously in this * circle position. At the moment this is basically removing a * refcount from the maps in it */ for(x = 0 ; x < bucket->hitsize ; x++) { hit = &bucket->hits[x]; if (hit->host_pos) hm->sub_ref(hit->host_pos); if (hit->url_pos) um->sub_ref(hit->url_pos); if (hit->ref_pos) rm->sub_ref(hit->ref_pos); } /* start at the beginning of the HIT array */ bucket->hitpos = 0; /* and ensure they're all zero'ed */ memset(bucket->hits, 0, sizeof(HIT) * bucket->hitsize); } int Timed_Circle::insert(struct logbits lb) { short rc_tmp; HIT *hit; struct timed_circle_struct *bucket; /* if we're in a new second than the current bucket is for.. */ // also updates bucketpos this->garbagecollection(); /* for the relevant bucket for this second.. */ bucket = &tab[bucketpos]; /* see if we have a free slot */ if (bucket->hitpos == bucket->hitsize) { /* we don't, increase the size of the hits array */ bucket->hits = (HIT *) realloc(bucket->hits, sizeof(HIT) * bucket->hitsize*2); if (!bucket->hits) { dprintf("realloc: %s\n", strerror(errno)); abort(); /* FIXME: handle a bit better */ } /* clear out the new hit positions */ memset(&bucket->hits[bucket->hitpos], 0, sizeof(HIT) * bucket->hitsize); bucket->hitsize *= 2; } /* we do (now) */ hit = &(bucket->hits[bucket->hitpos]); /* free up any storage associated with the hit previously * in this circle position. At the moment this is basically * removing a refcount from the maps in it */ if (hit->host_pos) hm->sub_ref(hit->host_pos); if (hit->url_pos) um->sub_ref(hit->url_pos); if (hit->ref_pos) rm->sub_ref(hit->ref_pos); /* store the data itself */ memcpy(hit, &lb, sizeof(lb)); /* update stats for this bucket */ bucket->reqcount++; bucket->bytecount += lb.bytes; rc_tmp = (int)lb.retcode/100; bucket->rc_summary[rc_tmp]++; /* ready for our next hitpos */ bucket->hitpos++; return 0; } //int Timed_Circle::walk(unsigned int *url_pos, unsigned int *ip_pos, // int *bytes, time_t *time, unsigned int *ipl, unsigned int *retcode) int Timed_Circle::walk(struct logbits **lb) { struct timed_circle_struct *bucket; *lb = NULL; //dprintf("entering walk at bucket %d, hit %d\n", walk_bucketpos, walk_hitpos); /* see if we're at the end of this bucket */ bucket = &tab[walk_bucketpos]; if (walk_hitpos == bucket->hitpos) { //dprintf("end of bucket %d\n", walk_bucketpos); /* next bucket */ ++walk_bucketpos; walk_hitpos = 0; } /* see if we're out of buckets; * bucketsize-1 is the highest we'll ever use */ if (walk_bucketpos == MAX_BUCKET) { /* done */ walk_bucketpos = 0; walk_hitpos = 0; return -1; } bucket = &tab[walk_bucketpos]; /* anything in this bucket? */ while (walk_bucketpos < MAX_BUCKET && bucket->hitpos == 0) { //dprintf("skipping bucket %d\n", walk_bucketpos); /* try the next one */ ++walk_bucketpos; walk_hitpos = 0; bucket = &tab[walk_bucketpos]; //dprintf("skipped to bucket %d\n", walk_bucketpos); /* see if we're out of buckets; * bucketsize-1 is the highest we'll ever use */ if (walk_bucketpos == MAX_BUCKET) { /* done */ walk_bucketpos = 0; walk_hitpos = 0; return -1; } } *lb = &(bucket->hits[walk_hitpos]); ++walk_hitpos; return 0; } void Timed_Circle::updatestats(void) { unsigned int x; struct timed_circle_struct *bucket; // clear any buckets between last hit and now this->garbagecollection(); /* now update stats from all buckets with something in them */ reqcount = 0; bytecount = 0; rc_summary[2] = rc_summary[3] = rc_summary[4] = rc_summary[5] = 0; for(x = 0, bucket = &tab[0] ; x < bucketsize ; ++x, ++bucket) { reqcount += bucket->reqcount; bytecount += bucket->bytecount; rc_summary[2] += bucket->rc_summary[2]; rc_summary[3] += bucket->rc_summary[3]; rc_summary[4] += bucket->rc_summary[4]; rc_summary[5] += bucket->rc_summary[5]; } } void Timed_Circle::garbagecollection(void) { unsigned int x, prevbucket; /* clear out the hits from any buckets which we've passed since our * last garbage collection. Basically, if a period of time passes in * between hits, we may go from bucket 1 .. 5 and skip 2/3/4. We * need to empty them here. */ if (tab[bucketpos].time < now) { prevbucket = bucketpos; bucketpos = (now - gstats.start) % bucketsize; if (bucketpos < prevbucket) { /* we've gone from end of array to start again */ /* so first, 0 to wherever */ for (x = 0 ; x <= bucketpos ; ++x) resetbucketstats(x); /* now from old position to end */ for (x = prevbucket+1 ; x < MAX_BUCKET ; ++x) resetbucketstats(x); } else { /* simpler, just (eg) 7 to 9 */ for(x = prevbucket+1 ; x <= bucketpos ; ++x) resetbucketstats(x); } // if we're overrunning, time to loop round // if (bucketpos == bucketsize) // bucketpos = 0; // back to first bucket } } time_t Timed_Circle::oldest(void) { time_t t = now; unsigned int x; struct timed_circle_struct *ptr; /* this may take up some considerable time with a large circle? */ for(x = 0, ptr = &tab[0] ; x < MAX_BUCKET ; ++x, ++ptr) t = getMIN(ptr->time, t); return t; } apachetop-0.19.7/src/timed_circle.h000066400000000000000000000030021351455032200171320ustar00rootroot00000000000000#ifndef _TIMED_CIRCLE_H_ #define _TIMED_CIRCLE_H_ /* store hits for a given second in here. * some bits of the logbits struct aren't used in the circle but * it's better than making an entirely new struct */ typedef struct logbits HIT; class Timed_Circle : public Circle { public: int create(unsigned int size); int insert(struct logbits lb); int walk(struct logbits **lb); void updatestats(void); time_t oldest(void); double getreqcount(void) { return reqcount; } double getbytecount(void) { return bytecount; } double getsummary(int r_c) { return rc_summary[r_c]; } private: int initbuckets(const unsigned int from, const unsigned int to); void resetbucketstats(const unsigned int r); void garbagecollection(void); double reqcount, bytecount; double rc_summary[6]; unsigned int bucketsize; /* how many buckets (seconds) ? */ unsigned int bucketpos; /* which bucket are we using now? */ /* the timed_circle_struct is a set of buckets; each bucket contains * data about hits for a given second. Each hit is stored in a * HIT array inside the relevant bucket */ struct timed_circle_struct { time_t time; /* second this bucket represents */ /* stats for the HITs array */ double reqcount, bytecount; double rc_summary[6]; unsigned int hitsize; /* how big is the array for hits? */ unsigned int hitpos; /* how far along hits array we are */ HIT *hits; /* hits for this second go into array */ } *tab; /* for walk() */ unsigned int walk_bucketpos; unsigned int walk_hitpos; }; #endif