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