source: trunk/EventBenchConsole/src/de/ugoe/cs/eventbench/web/WeblogParser.java @ 302

Last change on this file since 302 was 232, checked in by sherbold, 13 years ago
  • extended de.ugoe.cs.eventbench.web.WeblogParser? with functionality to determine frequent users
  • adapted command loadSessionsFromClickstream allow optional determination of frequent users
  • Property svn:mime-type set to text/plain
File size: 11.6 KB
Line 
1package de.ugoe.cs.eventbench.web;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.net.URI;
6import java.net.URISyntaxException;
7import java.text.ParseException;
8import java.text.SimpleDateFormat;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15import java.util.Map;
16import java.util.Set;
17
18import de.ugoe.cs.eventbench.web.data.WebEvent;
19import de.ugoe.cs.util.FileTools;
20import de.ugoe.cs.util.console.Console;
21
22/**
23 * <p>
24 * Provides functionality to parse log files with web request.
25 * </p>
26 *
27 * @author Steffen Herbold
28 * @version 1.0
29 */
30public class WeblogParser {
31
32        /**
33         * <p>
34         * Timeout between two sessions in milliseconds.
35         * </p>
36         */
37        private long timeout;
38
39        /**
40         * <p>
41         * Minimal length of a session. All shorter sessions will be pruned.<br>
42         * Default: 2
43         * </p>
44         */
45        private int minLength = 2;
46
47        /**
48         * <p>
49         * Maximal length of a session. All longer sessions will be pruned.<br>
50         * Default: 100
51         * </p>
52         */
53        private int maxLength = 100;
54
55        /**
56         * <p>
57         * URL of the server that generated the log that is currently parser; null
58         * of URL is not available.<br>
59         * Default: null
60         * </p>
61         */
62        private String url = null;
63
64        /**
65         * <p>
66         * Collection of generated sequences.
67         * </p>
68         */
69        private List<List<WebEvent>> sequences;
70
71        /**
72         * <p>
73         * List that stores the users (identified through their cookie id) to each
74         * sequence.
75         * </p>
76         */
77        private List<String> users;
78
79        /**
80         * <p>
81         * List that stores the frequent users (identified through their cookie id)
82         * to each sequence.
83         * </p>
84         */
85        private List<String> frequentUsers;
86
87        /**
88         * <p>
89         * Sequences for all frequent users.
90         * </p>
91         */
92        private List<Collection<List<WebEvent>>> sequencesFrequentUsers;
93
94        /**
95         * <p>
96         * Threshold that defines how many sessions of a user are require to deem
97         * the user frequent. Note, that only sessions whose lengths is in range if
98         * {@link #minLength} and {@link #maxLength} are counted.
99         * </p>
100         */
101        private int frequentUsersThreshold = -1;
102
103        /**
104         * <p>
105         * Name and path of the robot filter.
106         * </p>
107         */
108        private static final String ROBOTFILTERFILE = "misc/robotfilter.txt";
109
110        /**
111         * <p>
112         * Field that contains a regular expression that matches all robots
113         * contained in {@link #ROBOTFILTERFILE}.
114         * </p>
115         */
116        private String robotRegex = null;
117
118        /**
119         * <p>
120         * Constructor. Creates a new WeblogParser with a default timeout of
121         * 3,600,000 milliseconds (1 hour).
122         * </p>
123         */
124        public WeblogParser() {
125                this(3600000);
126        }
127
128        /**
129         * <p>
130         * Constructor. Creates a new WeblogParser.
131         * </p>
132         *
133         * @param timeout
134         *            session timeout
135         */
136        public WeblogParser(long timeout) {
137                this.timeout = timeout;
138        }
139
140        /**
141         * <p>
142         * Returns the generated event sequences.
143         * </p>
144         *
145         * @return generated event sequences
146         */
147        public Collection<List<WebEvent>> getSequences() {
148                return sequences;
149        }
150
151        /**
152         * <p>
153         * Sets the session timeout.
154         * </p>
155         *
156         * @param timeout
157         *            new session timeout
158         */
159        public void setTimeout(long timeout) {
160                this.timeout = timeout;
161        }
162
163        /**
164         * <p>
165         * Sets the minimal length of a session. All sessions that contain less
166         * events will be pruned.
167         * </p>
168         *
169         * @param minLength
170         *            new minimal length
171         */
172        public void setMinLength(int minLength) {
173                this.minLength = minLength;
174        }
175
176        /**
177         * <p>
178         * Sets the maximal length of a session. All sessions that contain more
179         * events will be pruned.
180         * </p>
181         *
182         * @param maxLength
183         *            new maximal length
184         */
185        public void setMaxLength(int maxLength) {
186                this.maxLength = maxLength;
187        }
188
189        /**
190         * <p>
191         * Sets the URL of the server from which this log was generated. Often
192         * required for replay generation
193         * </p>
194         *
195         * @param url
196         *            URL of the server
197         */
198        public void setUrl(String url) {
199                this.url = url;
200        }
201
202        /**
203         * <p>
204         * Sets the threshold for frequent users.
205         * </p>
206         *
207         * @param threshold
208         *            threshold value; if the value is &lt;1, the sessions of the
209         *            frequent users will not be determined
210         */
211        public void setFrequentUserThreshold(int threshold) {
212                this.frequentUsersThreshold = threshold;
213        }
214
215        /**
216         * <p>
217         * Returns the IDs of all frequent users.
218         * </p>
219         *
220         * @return IDs of the frequent users
221         */
222        public List<String> getFrequentUsers() {
223                return frequentUsers;
224        }
225
226        /**
227         * <p>
228         * Returns the sequences of all frequent users.
229         * </p>
230         * </p>
231         *
232         * @return list of the sequences of all frequent users
233         */
234        public List<Collection<List<WebEvent>>> getFrequentUserSequences() {
235                return sequencesFrequentUsers;
236        }
237
238        /**
239         * <p>
240         * Parses a web log file.
241         * </p>
242         *
243         * @param filename
244         *            name and path of the log file
245         * @throws IOException
246         *             thrown if there is a problem with reading the log file
247         * @throws FileNotFoundException
248         *             thrown if the log file is not found
249         * @throws ParseException
250         *             thrown the date format is invalid
251         */
252        public void parseFile(String filename) throws IOException,
253                        FileNotFoundException, ParseException {
254                String[] lines = FileTools.getLinesFromFile(filename);
255
256                Map<String, List<Integer>> cookieSessionMap = new HashMap<String, List<Integer>>();
257                int lastId = -1;
258
259                SimpleDateFormat dateFormat = new SimpleDateFormat(
260                                "yyyy-MM-dd HH:mm:ss");
261                loadRobotRegex();
262
263                sequences = new ArrayList<List<WebEvent>>();
264                users = new ArrayList<String>();
265
266                int lineCounter = 0;
267                for (String line : lines) {
268                        lineCounter++;
269                        String[] values = line.substring(1, line.length() - 1).split(
270                                        "\" \"");
271
272                        // use cookie as session identifier
273                        int cookieStart = values[0].lastIndexOf('.');
274                        String cookie = values[0].substring(cookieStart + 1);
275                        String dateString = values[1];
276                        long timestamp = dateFormat.parse(dateString).getTime();
277                        String uriString = values[2];
278                        // String ref = values[3]; // referer is not yet used!
279                        String agent;
280                        if (values.length > 4) {
281                                agent = values[4];
282                        } else {
283                                agent = "noagent";
284                        }
285
286                        List<String> postedVars = new ArrayList<String>();
287                        if (values.length == 6) { // post vars found
288                                for (String postVar : values[5].trim().split(" ")) {
289                                        // TODO manual filtering of bad variables, should be
290                                        // automated
291                                        if (!postVar.contains("and")) {
292                                                postedVars.add(postVar);
293                                        }
294                                }
295                        }
296                        if (!isRobot(agent)) {
297                                try {
298                                        URI uri = new URI(uriString);
299                                        String path = uri.getPath();
300                                        List<String> getVars = extractGetVarsFromUri(uri);
301
302                                        WebEvent event = new WebEvent(url, path, timestamp,
303                                                        postedVars, getVars);
304
305                                        // find session and add event
306                                        List<Integer> sessionIds = cookieSessionMap.get(cookie);
307                                        if (sessionIds == null) {
308                                                sessionIds = new ArrayList<Integer>();
309                                                // start new session
310                                                sessionIds.add(++lastId);
311                                                cookieSessionMap.put(cookie, sessionIds);
312                                                sequences.add(new LinkedList<WebEvent>());
313                                                users.add(cookie);
314                                        }
315                                        Integer lastSessionIndex = sessionIds
316                                                        .get(sessionIds.size() - 1);
317                                        List<WebEvent> lastSession = sequences
318                                                        .get(lastSessionIndex);
319                                        long lastEventTime = timestamp;
320                                        if (!lastSession.isEmpty()) {
321                                                lastEventTime = lastSession.get(lastSession.size() - 1)
322                                                                .getTimestamp();
323                                        }
324                                        if (timestamp - lastEventTime > timeout) {
325                                                sessionIds.add(++lastId);
326                                                List<WebEvent> newSession = new LinkedList<WebEvent>();
327                                                newSession.add(event);
328                                                sequences.add(newSession);
329                                                users.add(cookie);
330                                        } else {
331                                                lastSession.add(event);
332                                        }
333                                } catch (URISyntaxException e) {
334                                        Console.traceln("Ignored line " + lineCounter + ": "
335                                                        + e.getMessage());
336                                }
337                        }
338                }
339                Console.traceln("" + sequences.size() + " user sequences found");
340                pruneSequences();
341                Console.traceln("" + sequences.size()
342                                + " remaining after pruning of sequences shorter than "
343                                + minLength);
344                Set<String> uniqueUsers = new HashSet<String>(users);
345                Console.traceln("" + uniqueUsers.size() + " unique users");
346                if (frequentUsersThreshold > 0) {
347                        generateFrequentUserSequences(uniqueUsers);
348                }
349        }
350
351        /**
352         * <p>
353         * Generates the frequent user sequences, according to the threshold
354         * {@link #frequentUsersThreshold}.
355         * </p>
356         *
357         * @param uniqueUsers
358         *            set with all user IDs
359         */
360        private void generateFrequentUserSequences(Set<String> uniqueUsers) {
361                frequentUsers = new ArrayList<String>();
362                sequencesFrequentUsers = new ArrayList<Collection<List<WebEvent>>>();
363                for (String user : uniqueUsers) {
364                        List<String> tmp = new ArrayList<String>();
365                        tmp.add(user);
366                        List<String> usersCopy = new LinkedList<String>(users);
367                        usersCopy.retainAll(tmp);
368                        int size = usersCopy.size();
369                        if (size >= frequentUsersThreshold) {
370                                frequentUsers.add(user);
371                                Collection<List<WebEvent>> sequencesUser = new ArrayList<List<WebEvent>>();
372                                for (int i = 0; i < sequences.size(); i++) {
373                                        if (users.get(i).equals(user)) {
374                                                sequencesUser.add(sequences.get(i));
375                                        }
376                                }
377                                sequencesFrequentUsers.add(sequencesUser);
378
379                        }
380                }
381                Console.traceln("" + frequentUsers.size() + " users with more than "
382                                + frequentUsersThreshold + " sequences");
383        }
384
385        /**
386         * <p>
387         * Prunes sequences shorter than {@link #minLength} and longer than
388         * {@link #maxLength}.
389         * </p>
390         */
391        private void pruneSequences() {
392                int i = 0;
393                while (i < sequences.size()) {
394                        if ((sequences.get(i).size() < minLength)
395                                        || sequences.get(i).size() > maxLength) {
396                                sequences.remove(i);
397                                users.remove(i);
398                        } else {
399                                i++;
400                        }
401                }
402
403        }
404
405        /**
406         * <p>
407         * Reads {@link #ROBOTFILTERFILE} and creates a regular expression that
408         * matches all the robots defined in the file. The regular expression is
409         * stored in the field {@link #robotRegex}.
410         * </p>
411         *
412         * @throws IOException
413         *             thrown if there is a problem reading the robot filter
414         * @throws FileNotFoundException
415         *             thrown if the robot filter is not found
416         */
417        private void loadRobotRegex() throws IOException, FileNotFoundException {
418                String[] lines = FileTools.getLinesFromFile(ROBOTFILTERFILE);
419                StringBuilder regex = new StringBuilder();
420                for (int i = 0; i < lines.length; i++) {
421                        regex.append("(.*" + lines[i] + ".*)");
422                        if (i != lines.length - 1) {
423                                regex.append('|');
424                        }
425                }
426                robotRegex = regex.toString();
427        }
428
429        /**
430         * <p>
431         * Checks whether an agent is a robot.
432         * </p>
433         *
434         * @param agent
435         *            agent that is checked
436         * @return true, if the agent is a robot; false otherwise
437         */
438        private boolean isRobot(String agent) {
439                return agent.matches(robotRegex);
440        }
441
442        /**
443         * <p>
444         * Parses the URI and extracts the GET variables that have been passed.
445         * </p>
446         *
447         * @param uri
448         *            URI that is parsed
449         * @return a list with all GET variables
450         */
451        private List<String> extractGetVarsFromUri(URI uri) {
452                List<String> getVars = new ArrayList<String>();
453                String query = uri.getQuery();
454                if (query != null) {
455                        String[] paramPairs = query.split("&");
456                        for (String paramPair : paramPairs) {
457                                String[] paramSplit = paramPair.split("=");
458                                // TODO manual filtering of bad variables, should be automated
459                                if (!paramSplit[0].contains("and")) {
460                                        getVars.add(paramSplit[0]);
461                                }
462                        }
463                }
464                return getVars;
465        }
466}
Note: See TracBrowser for help on using the repository browser.