Added .gitattributes
[quix0rs-apt-p2p.git] / apt_p2p_Khashmir / stats.py
1
2 """Store statistics for the Khashmir DHT."""
3
4 from datetime import datetime, timedelta
5 from StringIO import StringIO
6
7 from ktable import K
8 from util import byte_format
9
10 class StatsLogger:
11     """Store the statistics for the Khashmir DHT.
12     
13     @ivar startTime: the time the program was started
14     @ivar reachable: whether we can be contacted by other nodes
15     @type table: L{ktable.KTable}
16     @ivar table: the routing table for the DHT
17     @ivar lastTableUpdate: the last time an update of the table stats was done
18     @ivar nodes: the number of nodes connected
19     @ivar users: the estimated number of total users in the DHT
20     @type store: L{db.DB}
21     @ivar store: the database for the DHT
22     @ivar lastDBUpdate: the last time an update of the database stats was done
23     @ivar keys: the number of distinct keys in the database
24     @ivar values: the number of values in the database
25     @ivar downPackets: the number of packets received
26     @ivar upPackets: the number of packets sent
27     @ivar downBytes: the number of bytes received
28     @ivar upBytes: the number of bytes sent
29     @ivar actions: a dictionary of the actions and their statistics, keys are
30         the action name, values are a list of 5 elements for the number of
31         times the action was sent, responded to, failed, received, and
32         generated an error
33     """
34     
35     def __init__(self, table, store):
36         """Initialize the statistics.
37         
38         @type table: L{ktable.KTable}
39         @param table: the routing table for the DHT
40         @type store: L{db.DB}
41         @param store: the database for the DHT
42         """
43         # General
44         self.startTime = datetime.now().replace(microsecond=0)
45         self.reachable = False
46         
47         # Routing Table
48         self.table = table
49         self.lastTableUpdate = datetime.now()
50         self.nodes = 0
51         self.users = 0
52         
53         # Database
54         self.store = store
55         self.lastDBUpdate = datetime.now()
56         self.keys = 0
57         self.values = 0
58         
59         # Transport
60         self.downPackets = 0
61         self.upPackets = 0
62         self.downBytes = 0L
63         self.upBytes = 0L
64         self.actions = {}
65     
66     def tableStats(self):
67         """Collect some statistics about the routing table.
68         
69         @rtype: (C{int}, C{int})
70         @return: the number of contacts in the routing table, and the estimated
71             number of nodes in the entire DHT
72         """
73         if datetime.now() - self.lastTableUpdate > timedelta(seconds = 15):
74             self.lastTableUpdate = datetime.now()
75             self.nodes = reduce(lambda a, b: a + b.len(), self.table.buckets, 0)
76             self.users = K * (2**(len(self.table.buckets) - 1))
77         return (self.nodes, self.users)
78     
79     def dbStats(self):
80         """Collect some statistics about the database.
81         
82         @rtype: (C{int}, C{int})
83         @return: the number of keys and values in the database
84         """
85         if datetime.now() - self.lastDBUpdate > timedelta(minutes = 1):
86             self.lastDBUpdate = datetime.now()
87             self.keys, self.values = self.store.keyStats()
88         return (self.keys, self.values)
89     
90     def formatHTML(self):
91         """Gather statistics for the DHT and format them for display in a browser.
92         
93         @rtype: C{string}
94         @return: the stats, formatted for display in the body of an HTML page
95         """
96         self.tableStats()
97         self.dbStats()
98         elapsed = datetime.now().replace(microsecond=0) - self.startTime
99         out = StringIO()
100         out.write('<h2>DHT Statistics</h2>\n')
101         out.write("<table border='0' cellspacing='20px'>\n<tr>\n")
102         out.write('<td>\n')
103
104         # General
105         out.write("<table border='1' cellpadding='4px'>\n")
106         out.write("<tr><th><h3>General</h3></th><th>Value</th></tr>\n")
107         out.write("<tr title='Elapsed time since the DHT was started'><td>Up time</td><td>" + str(elapsed) + '</td></tr>\n')
108         out.write("<tr title='Whether this node is reachable by other nodes'><td>Reachable</td><td>" + str(self.reachable) + '</td></tr>\n')
109         out.write("</table>\n")
110         out.write('</td><td>\n')
111         
112         # Routing
113         out.write("<table border='1' cellpadding='4px'>\n")
114         out.write("<tr><th><h3>Routing Table</h3></th><th>Value</th></tr>\n")
115         out.write("<tr title='The number of connected nodes'><td>Number of nodes</td><td>" + str(self.nodes) + '</td></tr>\n')
116         out.write("<tr title='The estimated number of connected users in the entire DHT'><td>Total number of users</td><td>" + str(self.users) + '</td></tr>\n')
117         out.write("</table>\n")
118         out.write('</td><td>\n')
119         
120         # Database
121         out.write("<table border='1' cellpadding='4px'>\n")
122         out.write("<tr><th><h3>Database</h3></th><th>Value</th></tr>\n")
123         out.write("<tr title='Number of distinct keys in the database'><td>Keys</td><td>" + str(self.keys) + '</td></tr>\n')
124         out.write("<tr title='Total number of values stored locally'><td>Values</td><td>" + str(self.values) + '</td></tr>\n')
125         out.write("</table>\n")
126         out.write("</td></tr><tr><td colspan='3'>\n")
127         
128         # Transport
129         out.write("<table border='1' cellpadding='4px'>\n")
130         out.write("<tr><th><h3>Transport</h3></th><th>Packets</th><th>Bytes</th><th>Speed</th></tr>\n")
131         out.write("<tr title='Stats for packets received from the DHT'><td>Downloaded</td>")
132         out.write('<td>' + str(self.downPackets) + '</td>')
133         out.write('<td>' + byte_format(self.downBytes) + '</td>')
134         out.write('<td>' + byte_format(self.downBytes / (elapsed.days*86400.0 + elapsed.seconds)) + '/sec</td></tr>\n')
135         out.write("<tr title='Stats for packets sent to the DHT'><td>Uploaded</td>")
136         out.write('<td>' + str(self.upPackets) + '</td>')
137         out.write('<td>' + byte_format(self.upBytes) + '</td>')
138         out.write('<td>' + byte_format(self.upBytes / (elapsed.days*86400.0 + elapsed.seconds)) + '/sec</td></tr>\n')
139         out.write("</table>\n")
140         out.write("</td></tr><tr><td colspan='3'>\n")
141         
142         # Actions
143         out.write("<table border='1' cellpadding='4px'>\n")
144         out.write("<tr><th><h3>Actions</h3></th><th>Started</th><th>Sent</th>")
145         out.write("<th>Successful</th><th>Failed</th><th>Completed</th><th>Received</th><th>Error</th>")
146         out.write("<th>Successful Delay</th><th>Failed Delay</th><th>Total Delay</th></tr>\n")
147         actions = self.actions.keys()
148         actions.sort()
149         for action in actions:
150             out.write("<tr><td>" + action + "</td>")
151             for i in xrange(7):
152                 out.write("<td>" + str(self.actions[action][i]) + "</td>")
153             for i in xrange(3):
154                 count = self.actions[action][i+2]
155                 if count > 0:
156                     total_delay = self.actions[action][i+7]
157                     avg_delay = total_delay / count
158                     avg_delay_sec = avg_delay.days*86400.0 + avg_delay.seconds + avg_delay.microseconds/1000000.0
159                 else:
160                     avg_delay_sec = 0.0
161                 out.write("<td>%0.2f</td>" % avg_delay_sec)
162             out.write('</tr>\n')
163         out.write("</table>\n")
164         out.write("</td></tr>\n")
165         out.write("</table>\n")
166         
167         return out.getvalue()
168
169     #{ Called by the action
170     def startedAction(self, action):
171         """Record that an action was started.
172         
173         @param action: the name of the action
174         """
175         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
176         act[0] += 1
177     
178     #{ Called by the transport
179     def sentAction(self, action):
180         """Record that an action was attempted.
181         
182         @param action: the name of the action
183         """
184         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
185         act[1] += 1
186         
187     def responseAction(self, response, action, start):
188         """Record that a response to an action was received.
189         
190         @param response: the response
191         @param action: the name of the action
192         @param start: the time the action was started
193         @return: the response (for use in deferreds)
194         """
195         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
196         act[2] += 1
197         act[7] += datetime.now() - start
198         return response
199         
200     def failedAction(self, response, action, start):
201         """Record that a failed response to an action was received.
202         
203         @param response: the response
204         @param action: the name of the action
205         @param start: the time the action was started
206         @return: the response (for use in deferreds)
207         """
208         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
209         act[3] += 1
210         act[8] += datetime.now() - start
211         return response
212         
213     def completedAction(self, action, start):
214         """Record that an action was completed.
215         
216         @param action: the name of the action
217         @param start: the time the action was started
218         """
219         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
220         act[4] += 1
221         act[9] += datetime.now() - start
222         
223     def receivedAction(self, action):
224         """Record that an action was received.
225         
226         @param action: the name of the action
227         """
228         self.reachable = True
229         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
230         act[5] += 1
231     
232     def errorAction(self, action):
233         """Record that a received action resulted in an error.
234         
235         @param action: the name of the action
236         """
237         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
238         act[6] += 1
239     
240     def sentBytes(self, bytes):
241         """Record that a single packet of some bytes was sent.
242         
243         @param bytes: the number of bytes in the packet
244         """
245         self.upPackets += 1
246         self.upBytes += bytes
247         
248     def receivedBytes(self, bytes):
249         """Record that a single packet of some bytes was received.
250         
251         @param bytes: the number of bytes in the packet
252         """
253         self.downPackets += 1
254         self.downBytes += bytes