ppf/new: Updates and new tests for parsing module.
[cs-p2p-next.git] / ppf / new / parsing.py
1 """
2 Parsers for P2P logging information.
3
4 Built on previous work by Adriana Draghici, Razvan Deaconescu
5 and Mariana Marasoiu.
6
7 2011, Razvan Deaconescu, razvan.deaconescu@cs.pub.ro
8 """
9
10 import os
11 import os.path
12 import re
13 import datetime
14 import logging
15 import socket
16
17 import storage      # Use *Message classes.
18
19 #
20 # Logging code heavily inspired by Logging HOWTO documentation:
21 #     http://docs.python.org/dev/howto/logging.html#configuring-logging
22 #
23
24 # Create logger; default logging level is DEBUG.
25 logger = logging.getLogger(__name__)
26 logger.setLevel(logging.DEBUG)
27
28 # Create console handler and set level to ERROR.
29 ch = logging.StreamHandler()
30 ch.setLevel(logging.DEBUG)
31
32 # Create formatter.
33 formatter = logging.Formatter('%(filename)s:%(lineno)s - %(levelname)s: %(message)s')
34
35 # Add formatter to console handler.
36 ch.setFormatter(formatter)
37
38 # Add console handler to logger.
39 logger.addHandler(ch)
40
41 class SessionLogParser(object):
42     """
43     Top-level class for parsing log file(s) for a given BitTorrent session.
44     """
45
46     def __init__(self, path):
47         self.path = path
48         # parsing: file currently being parsed
49         self.parsing = None
50
51     def get_next_message(self):
52         """
53         Find next message in log file/folder. May be status, peer status
54         or verbose message.
55         Return None when all logs have been parsed.
56         """
57         return None
58
59     def get_current_parsing_filename(self):
60         """Return the name of the log file being currently parsed."""
61         return self.parsing
62
63
64 class LibtorrentLogParser(SessionLogParser):
65     """
66     libtorrent-rasterbar log folder parser.
67     """
68
69     def __init__(self, path, priority=None):
70         """
71         If priority == "verbose" parse verbose log files first. Else, parse
72         status file first."
73         """
74         super(LibtorrentLogParser, self).__init__(path)
75
76         # to_parse: list of files to be parsed
77         # have_parsed: list of files that have been parsed
78         self.to_parse = []
79         self.have_parsed = []
80
81         self.f = None   # handler to file being parsed
82
83         for entry in os.listdir(self.path):
84             entry_path = os.path.join(self.path, entry)
85             if os.path.isfile(entry_path):
86                 # If entry is file and name is IP_PORT.log add it to list.
87                 if self.is_verbose_log_filename(entry):
88                     logger.debug("Add entry path %s." %(entry_path))
89                     self.to_parse.append(entry_path)
90
91         status_file_path = os.path.join(self.path, "status.log")
92         # In case status file doesn't exist, skip it.
93         if os.access(status_file_path, os.F_OK) and \
94                 os.access(status_file_path, os.R_OK):
95             logger.debug("Status file exists: %s." %(status_file_path))
96             # List functions as a stack. First files go to the end.
97             if priority == "verbose":
98                 self.to_parse.insert(0, status_file_path)
99             else:
100                 self.to_parse.append(status_file_path)
101
102         self.open_next_file()
103
104     def get_to_parse_list(self):
105         return self.to_parse
106
107     def get_have_parsed_list(self):
108         return self.have_parsed
109
110     def is_verbose_log_filename(self, filename):
111         r = re.compile(r'^[0-9]+(?:\.[0-9]+){3}_[0-9]+\.log$')
112         if not r.match(filename):
113             return False
114
115         # Check for valid IP address and port.
116         a = re.split('_', filename)
117         ip = a[0]
118         port = int(a[1].split('.')[0])
119
120         # Check valid port.
121         if port <= 0 or port > 65535:
122             return False
123
124         # Check valid IP address.
125         try:
126             socket.inet_aton(ip)
127         except socket.error, e:
128             return False
129
130         return True
131
132     def open_next_file(self):
133         """
134         Open next log file from to_parse list.
135         Update have_parsed and parsing accordingly.
136         """
137         if self.parsing is None:     # first call
138             pass
139         else:
140             self.f.close()
141             self.have_parsed.append(parsing)
142
143         self.parsing = self.to_parse.pop()
144         self.f = open(self.parsing, 'r')
145
146         # TODO: Log this information somewhere for snapshotting purpose.
147         # In case an error occurs parsing would resume from that point.
148
149     def is_status_log_line(self):
150         return False
151
152     def is_peer_status_log_line(self):
153         return False
154
155     def is_verbose_log_line(self):
156         return False
157
158     def parse_status_log_line(self):
159         return None
160
161     def parse_peer_status_log_line(self):
162         return None
163
164     def parse_verbose_log_line(self):
165         return None
166
167     def parse_log_line(self, line):
168         """Parse a log line and establish its type.
169
170         Type may be status, verbose or peer status.
171         Return message in line, in case of message line, or None in case
172         of no message line."""
173
174         # Check log line type and call appropriate method.
175         if self.is_status_log_line(line):
176             return self.parse_status_log_line(line)
177         elif self.is_peer_status_log_line(line):
178             return self.parse_peer_status_log_line(line)
179         elif self.is_verbose_log_line(line):
180             return self.parse_verbose_log_line(line)
181
182         # Return None in case of unknown/non-existent message type line.
183         return None
184
185     def get_next_message(self):
186         """
187         Go through all files in libtorrent log folder and parse them.
188         Return the next message available or None when all log files have
189         been parsed.
190         """
191
192         while True:             # Find first message.
193             while True:             # Find first available file.
194                 line = self.f.readline()
195                 if line is not None:
196                     break
197
198                 # end of file reached
199                 try:
200                     self.open_next_file()
201                 except IndexError, e:
202                     # In case of no more files, return None.
203                     return None
204                 else:
205                     continue
206
207             msg = self.parse_log_line(line)
208
209             # Go around in case line is bogus (msg is None).
210             if msg is not None:
211                 break
212
213         # Caller has to distinguish between message types.
214         return msg
215
216
217 class TriblerLogParser(SessionLogParser):
218     """
219     Tribler log file parser.
220     """
221
222     def __init__(self, path):
223         super(TriblerLogParser, self).__init__(path)
224
225     def get_next_message(self):
226         """
227         Go through Tribler log file. All log messages (verbose, status)
228         are stored in the same file.
229         Return the next message available or None when all messages have
230         been parsed.
231         """
232         return None