2 """Store statistics for the Khashmir DHT."""
4 from datetime import datetime, timedelta
5 from copy import deepcopy
8 """Store the statistics for the Khashmir DHT.
10 @ivar _StatsTemplate: a template for returning all the statistics
11 @type config: C{dictionary}
12 @ivar config: the configuration parameters for the 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 _StatsTemplate = [{'name': 'uptime',
38 'tip': 'The elapsed time since the program started',
44 'tip': 'Whether other nodes can contact us (not NATted or firewalled)',
48 'group': 'Routing Table',
49 'desc': 'Number of nodes',
50 'tip': 'The number of nodes we are connected to',
54 'group': 'Routing Table',
55 'desc': 'Total number of users',
56 'tip': 'The estimated total number of users in the DHT',
62 'tip': 'The number of distinct keys in the database',
68 'tip': 'The total number of values in the database',
71 {'name': 'downPackets',
73 'desc': 'Downloaded packets',
74 'tip': 'The number of received packets',
79 'desc': 'Uploaded packets',
80 'tip': 'The number of sent packets',
85 'desc': 'Downloaded bytes',
86 'tip': 'The number of bytes received by the DHT',
91 'desc': 'Uploaded bytes',
92 'tip': 'The number of bytes sent by the DHT',
97 'desc': 'Downloaded bytes/second',
98 'tip': 'The number of bytes received by the DHT per second',
102 'group': 'Transport',
103 'desc': 'Uploaded bytes/second',
104 'tip': 'The number of bytes sent by the DHT per second',
110 'tip': 'The number of requests for each action',
115 def __init__(self, table, store, config):
116 """Initialize the statistics.
118 @type table: L{ktable.KTable}
119 @param table: the routing table for the DHT
120 @type store: L{db.DB}
121 @param store: the database for the DHT
122 @type config: C{dictionary}
123 @param config: the configuration parameters for the DHT
127 self.startTime = datetime.now()
128 self.reachable = False
132 self.lastTableUpdate = datetime.now()
138 self.lastDBUpdate = datetime.now()
149 def tableStats(self):
150 """Collect some statistics about the routing table.
152 @rtype: (C{int}, C{int})
153 @return: the number of contacts in the routing table, and the estimated
154 number of nodes in the entire DHT
156 if datetime.now() - self.lastTableUpdate > timedelta(seconds = 15):
157 self.lastTableUpdate = datetime.now()
158 self.nodes = reduce(lambda a, b: a + len(b.l), self.table.buckets, 0)
159 self.users = self.config['K'] * (2**(len(self.table.buckets) - 1))
160 return (self.nodes, self.users)
163 """Collect some statistics about the database.
165 @rtype: (C{int}, C{int})
166 @return: the number of keys and values in the database
168 if datetime.now() - self.lastDBUpdate > timedelta(minutes = 1):
169 self.lastDBUpdate = datetime.now()
170 self.keys, self.values = self.store.keyStats()
171 return (self.keys, self.values)
174 """Gather all the statistics for the DHT.
176 @rtype: C{list} of C{dictionary}
177 @return: each dictionary has keys describing the statistic:
178 name, group, desc, tip, and value
182 stats = self._StatsTemplate[:]
183 elapsed = datetime.now() - self.startTime
185 val = getattr(self, stat['name'], None)
186 if stat['name'] == 'uptime':
187 stat['value'] = elapsed
188 elif stat['name'] == 'actions':
189 stat['value'] = deepcopy(self.actions)
190 elif stat['name'] == 'downSpeed':
191 stat['value'] = self.downBytes / (elapsed.days*86400.0 + elapsed.seconds + elapsed.microseconds/1000000.0)
192 elif stat['name'] == 'upSpeed':
193 stat['value'] = self.upBytes / (elapsed.days*86400.0 + elapsed.seconds + elapsed.microseconds/1000000.0)
194 elif val is not None:
199 #{ Called by the transport
200 def sentAction(self, action):
201 """Record that an action was attempted.
203 @param action: the name of the action
205 act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
208 def responseAction(self, response, action):
209 """Record that a response to an action was received.
211 @param response: the response
212 @param action: the name of the action
213 @return: the response (for use in deferreds)
215 act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
219 def failedAction(self, response, action):
220 """Record that a failed response to an action was received.
222 @param response: the response
223 @param action: the name of the action
224 @return: the response (for use in deferreds)
226 act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
230 def receivedAction(self, action):
231 """Record that an action was received.
233 @param action: the name of the action
235 self.reachable = True
236 act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
239 def errorAction(self, action):
240 """Record that a received action resulted in an error.
242 @param action: the name of the action
244 act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
247 def sentBytes(self, bytes):
248 """Record that a single packet of some bytes was sent.
250 @param bytes: the number of bytes in the packet
253 self.upBytes += bytes
255 def receivedBytes(self, bytes):
256 """Record that a single packet of some bytes was received.
258 @param bytes: the number of bytes in the packet
260 self.downPackets += 1
261 self.downBytes += bytes