Added the number of times each action was started to the DHT stats.
[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 copy import deepcopy
6
7 class StatsLogger:
8     """Store the statistics for the Khashmir DHT.
9     
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
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     _StatsTemplate = [{'name': 'uptime',
36                        'group': 'General',
37                        'desc': 'Up time',
38                        'tip': 'The elapsed time since the program started',
39                        'value': None,
40                        },
41                       {'name': 'reachable',
42                        'group': 'General',
43                        'desc': 'Reachable',
44                        'tip': 'Whether other nodes can contact us (not NATted or firewalled)',
45                        'value': None,
46                        },
47                       {'name': 'nodes',
48                        'group': 'Routing Table',
49                        'desc': 'Number of nodes',
50                        'tip': 'The number of nodes we are connected to',
51                        'value': None,
52                        },
53                       {'name': 'users',
54                        'group': 'Routing Table',
55                        'desc': 'Total number of users',
56                        'tip': 'The estimated total number of users in the DHT',
57                        'value': None,
58                        },
59                       {'name': 'keys',
60                        'group': 'Database',
61                        'desc': 'Keys',
62                        'tip': 'The number of distinct keys in the database',
63                        'value': None,
64                        },
65                       {'name': 'values',
66                        'group': 'Database',
67                        'desc': 'Values',
68                        'tip': 'The total number of values in the database',
69                        'value': None,
70                        },
71                       {'name': 'downPackets',
72                        'group': 'Transport',
73                        'desc': 'Downloaded packets',
74                        'tip': 'The number of received packets',
75                        'value': None,
76                        },
77                       {'name': 'upPackets',
78                        'group': 'Transport',
79                        'desc': 'Uploaded packets',
80                        'tip': 'The number of sent packets',
81                        'value': None,
82                        },
83                       {'name': 'downBytes',
84                        'group': 'Transport',
85                        'desc': 'Downloaded bytes',
86                        'tip': 'The number of bytes received by the DHT',
87                        'value': None,
88                        },
89                       {'name': 'upBytes',
90                        'group': 'Transport',
91                        'desc': 'Uploaded bytes',
92                        'tip': 'The number of bytes sent by the DHT',
93                        'value': None,
94                        },
95                       {'name': 'downSpeed',
96                        'group': 'Transport',
97                        'desc': 'Downloaded bytes/second',
98                        'tip': 'The number of bytes received by the DHT per second',
99                        'value': None,
100                        },
101                       {'name': 'upSpeed',
102                        'group': 'Transport',
103                        'desc': 'Uploaded bytes/second',
104                        'tip': 'The number of bytes sent by the DHT per second',
105                        'value': None,
106                        },
107                       {'name': 'actions',
108                        'group': 'Actions',
109                        'desc': 'Actions',
110                        'tip': 'The number of requests for each action',
111                        'value': None,
112                        },
113                       ]
114     
115     def __init__(self, table, store, config):
116         """Initialize the statistics.
117         
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
124         """
125         # General
126         self.config = config
127         self.startTime = datetime.now()
128         self.reachable = False
129         
130         # Routing Table
131         self.table = table
132         self.lastTableUpdate = datetime.now()
133         self.nodes = 0
134         self.users = 0
135         
136         # Database
137         self.store = store
138         self.lastDBUpdate = datetime.now()
139         self.keys = 0
140         self.values = 0
141         
142         # Transport
143         self.downPackets = 0
144         self.upPackets = 0
145         self.downBytes = 0L
146         self.upBytes = 0L
147         self.actions = {}
148     
149     def tableStats(self):
150         """Collect some statistics about the routing table.
151         
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
155         """
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)
161     
162     def dbStats(self):
163         """Collect some statistics about the database.
164         
165         @rtype: (C{int}, C{int})
166         @return: the number of keys and values in the database
167         """
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)
172     
173     def gather(self):
174         """Gather all the statistics for the DHT.
175         
176         @rtype: C{list} of C{dictionary}
177         @return: each dictionary has keys describing the statistic:
178             name, group, desc, tip, and value
179         """
180         self.tableStats()
181         self.dbStats()
182         stats = self._StatsTemplate[:]
183         elapsed = datetime.now() - self.startTime
184         for stat in stats:
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:
195                 stat['value'] = val
196                 
197         return stats
198     
199     #{ Called by the action
200     def startedAction(self, action):
201         """Record that an action was started.
202         
203         @param action: the name of the action
204         """
205         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
206         act[0] += 1
207     
208     #{ Called by the transport
209     def sentAction(self, action):
210         """Record that an action was attempted.
211         
212         @param action: the name of the action
213         """
214         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
215         act[1] += 1
216         
217     def responseAction(self, response, action):
218         """Record that a response to an action was received.
219         
220         @param response: the response
221         @param action: the name of the action
222         @return: the response (for use in deferreds)
223         """
224         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
225         act[2] += 1
226         return response
227         
228     def failedAction(self, response, action):
229         """Record that a failed response to an action was received.
230         
231         @param response: the response
232         @param action: the name of the action
233         @return: the response (for use in deferreds)
234         """
235         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
236         act[3] += 1
237         return response
238         
239     def receivedAction(self, action):
240         """Record that an action was received.
241         
242         @param action: the name of the action
243         """
244         self.reachable = True
245         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
246         act[4] += 1
247     
248     def errorAction(self, action):
249         """Record that a received action resulted in an error.
250         
251         @param action: the name of the action
252         """
253         act = self.actions.setdefault(action, [0, 0, 0, 0, 0, 0])
254         act[5] += 1
255     
256     def sentBytes(self, bytes):
257         """Record that a single packet of some bytes was sent.
258         
259         @param bytes: the number of bytes in the packet
260         """
261         self.upPackets += 1
262         self.upBytes += bytes
263         
264     def receivedBytes(self, bytes):
265         """Record that a single packet of some bytes was received.
266         
267         @param bytes: the number of bytes in the packet
268         """
269         self.downPackets += 1
270         self.downBytes += bytes