home *** CD-ROM | disk | FTP | other *** search
Java Source | 1998-11-05 | 22.6 KB | 558 lines |
- // Copyright (c) 1997, 1998 Symantec, Inc. All Rights Reserved.
-
- import java.io.Serializable;
- import java.util.Date;
- import java.util.Calendar;
- import java.util.Hashtable;
- import java.util.Vector;
- import java.util.NoSuchElementException;
- import java.util.Enumeration;
-
- /*
- This class encapsulates a single web log "report".
- It contains:
- 1) The report options (specified/changed by New/Edit Report dialogs),
- including a LogFile object representing the log file specified in the
- report options.
- 2) The report results (available after analyzing the log file).
- 3) Working fields (used during log file analysis).
- 4) Methods to analyze the log file and generate the results.
- Statistics are gathered over a day long interval (i.e. hits per day, etc) and
- also gathered over an interval chosen to be appropriate for the timespan of
- the report.
- It was written manually.
- It has no UI code.
- */
- public class Report implements Serializable {
- // -- The Report Options
- // possible timeframe choice values
- public static final int TIMEFRAME_ALL = 0;
- public static final int TIMEFRAME_TODAY = 1;
- public static final int TIMEFRAME_YESTERDAY = 2;
- public static final int TIMEFRAME_PAST_2_DAYS = 3;
- public static final int TIMEFRAME_PAST_7_DAYS = 4;
- public static final int TIMEFRAME_PAST_14_DAYS = 5;
- public static final int TIMEFRAME_PAST_30_DAYS = 6;
- public static final int TIMEFRAME_PAST_60_DAYS = 7;
- public static final int TIMEFRAME_PAST_90_DAYS = 8;
- public static final int TIMEFRAME_SPECIFIC = 9;
- String title;
- int timeframe; // TIMEFRAME_
- Date dateStart; // valid if timeframe == TIMEFRAME_SPECIFIC
- Date dateEnd; // valid if timeframe == TIMEFRAME_SPECIFIC
- LogFile log = new LogFile();
- String homePageFile;
- boolean optUsageChart;
- boolean optGeneralStatistics;
- boolean optMostActiveDomains;
- // -- The Report Results
- Date analyzedDate; // date report last run on
- long startTimestamp; // actual starting time (millisecs)
- long endTimestamp; // actual ending time (millisecs)
- int totalHits; // total number of hits
- int homePageHits; // hits on the home page
- int totalUserSessionCount; // total number of user sessions
- long avgUserSessionLength = 0; // average session length (millisecs)
- DomainRecord sortedDomains[]; // all domains, sorted by number of hits
- // "per day" statistics
- int hitsVsDay[]; // array to track hits VS day
- int userSessionsVsDay[]; // array to sessions VS day
- double avgHitsPerDay; // average hits per day
- double avgUserSessionsPerDay; // average sessions per day
- // "per <time interval>" statistics
- long hitTimeInterval; // interval statistics are gathered over (millisecs)
- int hitsVsTimeInterval[]; // array to track hits VS hitTimeInterval
- int userSessionsVsTimeInterval[];//array to sessions VS hitTimeInterval
- double avgHitsPerHitTimeInterval; // average number of hits per hitTimeInterval
- double avgUserSessionsPerHitTimeInterval; // average sessions per hitTimeInterval
- //SessionRecord sortedSessions[];
- // -- Working fields. Used during log file analysis
- String normalizedHomePageFile; // home page file with "/" added at start as needed and "\" changed to "/" as needed
- long newUserSessionThreshold; // user idle time to create a new session (millisecs)
- long timeTillFirstInterval; // millisecs till first full interval of time being analyzed
- long timeTillFirstDayInterval; // millisecs till first full day of time being analyzed
- long totalUserSessionLength; // total length in time of all user sessions
- Hashtable domains; // of DomainRecord
- Hashtable sessions; // of SessionRecord
- SessionRecord sessionListHead = null; // list of all SessionRecords
-
- /**
- * Constructs a Report.
- */
- public Report() {
- //{{INIT_CONTROLS
- //}}
- }
-
- /**
- * Frees up memory used to analyze (but not report) a web log.
- */
- void freeUpMem() {
- // Free up mem used to analyze, but not report, log info
- domains = null;
- sessions = null;
- sessionListHead = null;
- log.freeUpMem();
- }
-
- // -- ACCESS
-
- // Returns true if this report has been run (analyzed) before.
- public boolean hasBeenRun() {
- return analyzedDate != null;
- }
-
- // -- Handy shortcuts to get/set LogFile attributes
- public String getLogFileURL() {
- return log.getLogFileURL();
- }
- public void setLogFileURL(String logFileURL) {
- log.setLogFileURL(logFileURL);
- }
- public int getLogFileFormat() {
- return log.getLogFileFormat();
- }
- public void setLogFileFormat(int logFileFormat) {
- log.setLogFileFormat(logFileFormat);
- }
-
- // -- ANALYSIS
-
- /*
- Runs the report, analyzing the log file and gathering statistics.
- The TrackProgress interface is used to report analysis status to UI code.
- */
- public void run(TrackProgress trackProgress) {
- try {
-
- // -- Setup analysis
-
- // Reading and parse the log file, getting the results
- Analyzer.throwExceptionIfCurrentThreadCancelled();
- LogRecord records[] = log.readAndParseLogFile(trackProgress);
- if(records == null || records.length == 0) {
- trackProgress.okAlert("No valid HTTP records to process (wrong log file format?).");
- trackProgress.step("ABORTED", 0);
- // Note that we're done in the UI
- trackProgress.done();
- return;
- }
- // determine starting and ending time for this report
- Analyzer.throwExceptionIfCurrentThreadCancelled();
- startTimestamp = log.getEarliestTimestamp();
- endTimestamp = log.getLatestTimestamp();
- if(timeframe != TIMEFRAME_ALL) {
- startTimestamp = dateStart.getTime();
- endTimestamp = dateEnd.getTime();
- }
- //
- totalHits = 0;
- homePageHits = 0;
- totalUserSessionCount = 0;
- avgUserSessionLength = 0;
- sortedDomains = null;
- // init per day stats
- timeTillFirstDayInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_DAY, CAL_HOUR);
- // table: hitsVsDay
- int numTimeIntervals = 1 + timestampToDayIntervalIndex(endTimestamp);
- hitsVsDay = new int[numTimeIntervals];
- // table: userSessionsVsDay
- userSessionsVsDay = new int[numTimeIntervals];
- avgHitsPerDay = 0;
- avgUserSessionsPerDay = 0;
- // init per <time interval> stats
- // determine the <time interval> units: min, hour, day, week, or year
- long millisecs = endTimestamp - startTimestamp;
- if(millisecs <= WLAUtil.MILLISECS_PER_HOUR) {
- hitTimeInterval = WLAUtil.MILLISECS_PER_MINUTE;
- timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_MINUTE, CAL_SECOND);
- } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_DAY) {
- hitTimeInterval = WLAUtil.MILLISECS_PER_HOUR;
- timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_HOUR, CAL_MINUTE);
- } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_WEEK) {
- hitTimeInterval = WLAUtil.MILLISECS_PER_DAY;
- timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_DAY, CAL_HOUR);
- } else if(millisecs <= 2*WLAUtil.MILLISECS_PER_YEAR) {
- hitTimeInterval = WLAUtil.MILLISECS_PER_WEEK;
- timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_WEEK, CAL_DAY_OF_WEEK);
- } else {
- hitTimeInterval = WLAUtil.MILLISECS_PER_YEAR;
- timeTillFirstInterval = determineTimeTillFirstFullInterval(WLAUtil.MILLISECS_PER_YEAR, CAL_WEEK_OF_YEAR);
- }
- // table: hitsVsTimeInterval
- numTimeIntervals = 1 + timestampToIntervalIndex(endTimestamp);
- hitsVsTimeInterval = new int[numTimeIntervals];
- // table: userSessionsVsTimeInterval
- userSessionsVsTimeInterval = new int[numTimeIntervals];
- avgHitsPerHitTimeInterval = 0;
- avgUserSessionsPerHitTimeInterval = 0;
- // "normalize" home page file name
- int lim = homePageFile.length();
- if(lim == 0) {
- normalizedHomePageFile = homePageFile;
- } else {
- StringBuffer buf = new StringBuffer(lim + 1);
- int idx = 0;
- char ch = homePageFile.charAt(0);
- // if no leading slash, add one
- buf.append('/');
- if(ch == '/' || ch == java.io.File.separatorChar) {
- idx = 1;
- }
- while(idx < lim) {
- ch = homePageFile.charAt(idx++);
- if(ch == java.io.File.separatorChar) {
- buf.append('/');
- } else {
- buf.append(ch);
- }
- }
- normalizedHomePageFile = buf.toString();
- }
- // init remaining working fields
- newUserSessionThreshold = Data.getDataInstance().userSessionIdleLimit * WLAUtil.MILLISECS_PER_MINUTE;
- totalUserSessionLength = 0;
- domains = new Hashtable(); // of DomainRecord
- sessions = new Hashtable(); // of SessionRecord
- sessionListHead = null;
- // init local info
- int hitCount = 0;
- int totalLogHits = log.getTotalHits();
- // Done with analysis setup
-
- if(totalLogHits > 0) {
-
- // -- SCAN LOG LINES AND ACCUMULATE DATA
-
- for(int r = 0; r < totalLogHits; r++) {
- // percent for this phase ranges from 96 -> 100
- int percent = 96 + ((4 * hitCount)/totalLogHits);
- trackProgress.step("Processing log file...", percent);
- // Note the line's stats
- noteLogRecord(records[r]);
- }
-
- // -- CALC STATISTICS
-
- // Calc averages
- double timespan = endTimestamp - startTimestamp;
- timespan = timespan / WLAUtil.MILLISECS_PER_DAY;
- avgHitsPerDay = (double)totalHits / timespan;
- avgUserSessionsPerDay = (double)totalUserSessionCount / timespan;
- timespan = endTimestamp - startTimestamp;
- timespan = timespan / hitTimeInterval;
- avgHitsPerHitTimeInterval = (double)totalHits / timespan;
- avgUserSessionsPerHitTimeInterval = (double)totalUserSessionCount / timespan;
-
- // Calc total length of user sessions
- Analyzer.throwExceptionIfCurrentThreadCancelled();
- SessionRecord sr = sessionListHead;
- while(sr != null) {
- long delta = sr.lastTimestamp - sr.firstTimestamp;
- totalUserSessionLength += delta;
- sr = sr.next;
- }
-
- // Calc avg user session length
- if(totalUserSessionCount > 0) {
- avgUserSessionLength = totalUserSessionLength / totalUserSessionCount;
- }
-
- // Sort domains in order of most hits to least hits
- sortedDomains = new DomainRecord[domains.size()];
- // copy into array...
- int idx = 0;
- for(Enumeration e = domains.elements(); e.hasMoreElements();) {
- DomainRecord dr = (DomainRecord)e.nextElement();
- sortedDomains[idx++] = dr;
- }
- // ...and sort
- Analyzer.throwExceptionIfCurrentThreadCancelled();
- WLAUtil.quickSort(sortedDomains);
- }
-
- // -- DONE OK, note date/time report run
-
- analyzedDate = new Date(); // date report last run on
- trackProgress.step("Done", 100);
-
- } catch(NoSuchElementException x) {
- trackProgress.okAlert(x.toString());
- trackProgress.step("ABORTED", 0);
- } catch(ParseLogException x) {
- // These have already been reported.
- } catch(OutOfMemoryError x) {
- // free the main memory occupiers
- Data.freeUpMem();
- trackProgress.okAlert(x.toString());
- trackProgress.step("ABORTED", 0);
- }
-
- // Note that we're done in the UI
- trackProgress.done();
- }
-
- /*
- Called by the run() method for each log record to analyze.
- Accumulates report statistics.
- */
- void noteLogRecord(LogRecord logRec) throws ParseLogException {
- Analyzer.throwExceptionIfCurrentThreadCancelled();
-
- // -- Filter out undesired log records
-
- // Does this record occur within the timeframe we're analyzing??
- if(startTimestamp > logRec.requestTimestamp || logRec.requestTimestamp > endTimestamp) {
- // No. Ignore it.
- return;
- }
- // HTTP error on this request?
- if(logRec.httpStatusCode >= 500) {
- // Yes. Ignore it.
- return;
- }
- // Determine type of HTTP request
- int idx = logRec.clientRequest.indexOf(' ');
- if(idx < 0) {
- // Unexpected clientRequest. Ignore
- return;
- }
- String str = logRec.clientRequest.substring(0, idx);
- if(!str.equals("GET")) {
- // Ignore non-GET HTTP requests
- return;
- }
-
- // -- Use this log record
-
- // Note info for General Statistics
- totalHits++;
-
- // Note info for Hits VS Time chart
- int hitInterval = timestampToIntervalIndex(logRec.requestTimestamp);
- hitsVsTimeInterval[hitInterval]++;
-
- // Note info for Hits VS Day for day-related averages
- hitInterval = timestampToDayIntervalIndex(logRec.requestTimestamp);
- hitsVsDay[hitInterval]++;
-
- // Request for home page?
- int idx2 = logRec.clientRequest.indexOf(' ', ++idx);
- if(idx2 > 0) {
- str = logRec.clientRequest.substring(idx, idx2);
- if(str.equals("/") || str.equalsIgnoreCase(normalizedHomePageFile)) {
- homePageHits++;
- }
- }
-
- // Track # of hits for each domain
- DomainRecord dr = (DomainRecord)domains.get(logRec.remoteHost);
- if(dr == null) {
- dr = new DomainRecord(logRec.remoteHost);
- domains.put(logRec.remoteHost, dr);
- }
- // update the domain record
- dr.hits++;
-
- // -- Track sessions
-
- String sessionStr = logRec.remoteHost + " " + logRec.remoteUserLogname + " " + logRec.authenticatedUserName;
- SessionRecord sr = (SessionRecord)sessions.get(sessionStr);
-
- if(sr == null
- || (logRec.requestTimestamp - sr.lastTimestamp >= newUserSessionThreshold)) {
-
- // New user session
- sr = new SessionRecord(sessionStr, logRec);
- // Add to linked list of all Session Records
- sr.next = sessionListHead;
- sessionListHead = sr;
- // Update the hashtable which tracks current "live" sessions
- sessions.put(sessionStr, sr);
- totalUserSessionCount++; // total number of user sessions
-
- // Track userSessions Vs TimeInterval and Vs Day
-
- // Note info for Hits VS Time chart
- hitInterval = timestampToIntervalIndex(logRec.requestTimestamp);
- userSessionsVsTimeInterval[hitInterval]++;
-
- // Note info for Hits VS Day for day-related averages
- hitInterval = timestampToDayIntervalIndex(logRec.requestTimestamp);
- userSessionsVsDay[hitInterval]++;
- }
- // update the session record
- sr.hits++;
- sr.lastTimestamp = logRec.requestTimestamp;
- }
-
- /* determineTimeTillFirstFullInterval()
- Time intervals are aligned on natural boundries (i.e. midnight for a
- day long interval). This method determines the length of the first
- interval such that it ends on a natural boundry. Typically it is smaller
- than a full interval.
- */
- private static final int CAL_SECOND = 0;
- private static final int CAL_MINUTE = 1;
- private static final int CAL_HOUR = 2;
- private static final int CAL_DAY_OF_WEEK = 3;
- private static final int CAL_WEEK_OF_YEAR = 4;
- private static final int CALENDAR_UNIT_VALUES[] = {
- Calendar.SECOND,
- Calendar.MINUTE,
- Calendar.HOUR_OF_DAY,
- Calendar.DAY_OF_WEEK,
- Calendar.WEEK_OF_YEAR
- };
- private long determineTimeTillFirstFullInterval(long MILLISECS_PER_, int CAL_) {
- // Get a calendar and set its time to the report starting time + interval length
- Calendar cal = Calendar.getInstance();
- cal.setTime(new Date(startTimestamp+MILLISECS_PER_));
- // zero the appropriate fields, truncating time to interval natural boundry
- do {
- cal.set(CALENDAR_UNIT_VALUES[CAL_], 0);
- } while(--CAL_ >= 0);
- // Get resultant time, and calculate delta to that time from report starting time
- Date date = cal.getTime();
- long deltaTime = date.getTime() - startTimestamp;
- // done
- return deltaTime;
- }
-
- /*
- Calculates the day interval index that the given time falls into.
- */
- private int timestampToDayIntervalIndex(long timestamp) {
- int intervalIndex = 0;
- long ms = timestamp - startTimestamp;
- if(ms >= timeTillFirstDayInterval) {
- ms -= timeTillFirstDayInterval;
- intervalIndex = (int)(ms / WLAUtil.MILLISECS_PER_DAY) + 1;
- }
- return intervalIndex;
- }
-
- /*
- Calculates the <time interval> index that the given time falls into.
- */
- private int timestampToIntervalIndex(long timestamp) {
- int intervalIndex = 0;
- long ms = timestamp - startTimestamp;
- if(ms >= timeTillFirstInterval) {
- ms -= timeTillFirstInterval;
- intervalIndex = (int)(ms / hitTimeInterval) + 1;
- }
- return intervalIndex;
- }
-
-
- /*
- This class tracks info on a single user session.
- All SessionRecords are kept on a linked-list (sessionListHead refers
- to the most recently created SessionRecord).
- This class implements the Sortable interface to allow the quicksort
- utility routine to sort it.
- */
- class SessionRecord implements Serializable, Sortable {
- // The next SessionRecord in the linked-list.
- SessionRecord next = null;
- // This session's identifying string (a concatenation of the domain
- // name, remote user log name, and authenticated user name)
- String sessionStr;
- // The number of hits by this session
- int hits;
- // The session's starting time
- long firstTimestamp;
- // The time of this session's last request
- long lastTimestamp;
-
- /*
- Constructs a new session.
- */
- SessionRecord(String sessionStr, LogRecord logRec) {
- this.sessionStr = sessionStr;
- firstTimestamp = logRec.requestTimestamp;
- lastTimestamp = logRec.requestTimestamp;
- hits = 0;
- }
-
- // -- Implement the Sortable interface
-
- // A Sortable interface method.
- // Returns true if this "is before" the given obj
- public boolean isLessThan(Object obj) {
- if(obj instanceof SessionRecord) {
- SessionRecord r = (SessionRecord)obj;
- if(hits != r.hits) {
- return hits < r.hits;
- }
- return sessionStr.compareTo(r.sessionStr) > 0;
- }
- return false;
- }
-
- // A Sortable interface method.
- // Returns true if this "is before or equal to" the given obj
- public boolean isLessThanOrEqual(Object obj) {
- if(obj instanceof SessionRecord) {
- SessionRecord r = (SessionRecord)obj;
- if(hits != r.hits) {
- return hits < r.hits;
- }
- return sessionStr.compareTo(r.sessionStr) >= 0;
- }
- return false;
- }
- }
-
-
- /*
- This class tracks info on a single user session.
- This class implements the Sortable interface to allow the quicksort
- utility routine to sort it.
- */
- class DomainRecord implements Serializable, Sortable {
- // The domain name
- String domain;
- // The number of hits this domain has generated
- int hits;
-
- // Creates a new DomainRecord with the given name.
- DomainRecord(String domain) {
- this.domain = domain;
- }
-
- // -- Implement the Sortable interface
-
- // A Sortable interface method.
- // Returns true if this "is before" the given obj
- public boolean isLessThan(Object obj) {
- if(obj instanceof DomainRecord) {
- DomainRecord r = (DomainRecord)obj;
- if(hits != r.hits) {
- return hits < r.hits;
- }
- return domain.compareTo(r.domain) < 0;
- }
- return false;
- }
-
- // A Sortable interface method.
- // Returns true if this "is before or equal to" the given obj
- public boolean isLessThanOrEqual(Object obj) {
- if(obj instanceof DomainRecord) {
- DomainRecord r = (DomainRecord)obj;
- if(hits != r.hits) {
- return hits < r.hits;
- }
- return domain.compareTo(r.domain) <= 0;
- }
- return false;
- }
- }
-
- //{{DECLARE_CONTROLS
- //}}
- }
-
-