2 """Store statistics for the Khashmir DHT."""
4 from datetime import datetime, timedelta
5 from StringIO import StringIO
8 from util import byte_format
11 """Store the statistics for the Khashmir DHT.
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
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
35 def __init__(self, table, store):
36 """Initialize the statistics.
38 @type table: L{ktable.KTable}
39 @param table: the routing table for the DHT
41 @param store: the database for the DHT
44 self.startTime = datetime.now().replace(microsecond=0)
45 self.reachable = False
49 self.lastTableUpdate = datetime.now()
55 self.lastDBUpdate = datetime.now()
67 """Collect some statistics about the routing table.
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
73 if datetime.now() - self.lastTableUpdate > timedelta(seconds = 15):
74 self.lastTableUpdate = datetime.now()
75 self.nodes = reduce(lambda a, b: a + len(b.l), self.table.buckets, 0)
76 self.users = K * (2**(len(self.table.buckets) - 1))
77 return (self.nodes, self.users)
80 """Collect some statistics about the database.
82 @rtype: (C{int}, C{int})
83 @return: the number of keys and values in the database
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)
91 """Gather statistics for the DHT and format them for display in a browser.
94 @return: the stats, formatted for display in the body of an HTML page
98 elapsed = datetime.now().replace(microsecond=0) - self.startTime
100 out.write('<h2>DHT Statistics</h2>\n')
101 out.write("<table border='0' cellspacing='20px'>\n<tr>\n")
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')
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')
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")
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")
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()
149 for action in actions:
150 out.write("<tr><td>" + action + "</td>")
152 out.write("<td>" + str(self.actions[action][i]) + "</td>")
154 count = self.actions[action][i+2]
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
161 out.write("<td>%0.2f</td>" % avg_delay_sec)
163 out.write("</table>\n")
164 out.write("</td></tr>\n")
165 out.write("</table>\n")
167 return out.getvalue()
169 #{ Called by the action
170 def startedAction(self, action):
171 """Record that an action was started.
173 @param action: the name of the action
175 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
178 #{ Called by the transport
179 def sentAction(self, action):
180 """Record that an action was attempted.
182 @param action: the name of the action
184 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
187 def responseAction(self, response, action, start):
188 """Record that a response to an action was received.
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)
195 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
197 act[7] += datetime.now() - start
200 def failedAction(self, response, action, start):
201 """Record that a failed response to an action was received.
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)
208 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
210 act[8] += datetime.now() - start
213 def completedAction(self, action, start):
214 """Record that an action was completed.
216 @param action: the name of the action
217 @param start: the time the action was started
219 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
221 act[9] += datetime.now() - start
223 def receivedAction(self, action):
224 """Record that an action was received.
226 @param action: the name of the action
228 self.reachable = True
229 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
232 def errorAction(self, action):
233 """Record that a received action resulted in an error.
235 @param action: the name of the action
237 act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0, 0, timedelta(), timedelta(), timedelta()])
240 def sentBytes(self, bytes):
241 """Record that a single packet of some bytes was sent.
243 @param bytes: the number of bytes in the packet
246 self.upBytes += bytes
248 def receivedBytes(self, bytes):
249 """Record that a single packet of some bytes was received.
251 @param bytes: the number of bytes in the packet
253 self.downPackets += 1
254 self.downBytes += bytes