7a40adb28894f98845ad27a5c586a964e976dcc1
[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': 'actions',
96                        'group': 'Actions',
97                        'desc': 'Actions',
98                        'tip': 'The number of requests for each action',
99                        'value': None,
100                        },
101                       ]
102     
103     def __init__(self, table, store, config):
104         """Initialize the statistics.
105         
106         @type table: L{ktable.KTable}
107         @param table: the routing table for the DHT
108         @type store: L{db.DB}
109         @param store: the database for the DHT
110         @type config: C{dictionary}
111         @param config: the configuration parameters for the DHT
112         """
113         # General
114         self.config = config
115         self.startTime = datetime.now()
116         self.reachable = False
117         
118         # Routing Table
119         self.table = table
120         self.lastTableUpdate = datetime.now()
121         self.nodes = 0
122         self.users = 0
123         
124         # Database
125         self.store = store
126         self.lastDBUpdate = datetime.now()
127         self.keys = 0
128         self.values = 0
129         
130         # Transport
131         self.downPackets = 0
132         self.upPackets = 0
133         self.downBytes = 0L
134         self.upBytes = 0L
135         self.actions = {}
136     
137     def tableStats(self):
138         """Collect some statistics about the routing table.
139         
140         @rtype: (C{int}, C{int})
141         @return: the number of contacts in the routing table, and the estimated
142             number of nodes in the entire DHT
143         """
144         if datetime.now() - self.lastTableUpdate > timedelta(seconds = 15):
145             self.lastTableUpdate = datetime.now()
146             self.nodes = reduce(lambda a, b: a + len(b.l), self.table.buckets, 0)
147             self.users = self.config['K'] * (2**(len(self.table.buckets) - 1))
148         return (self.nodes, self.users)
149     
150     def dbStats(self):
151         """Collect some statistics about the database.
152         
153         @rtype: (C{int}, C{int})
154         @return: the number of keys and values in the database
155         """
156         if datetime.now() - self.lastDBUpdate > timedelta(minutes = 1):
157             self.lastDBUpdate = datetime.now()
158             self.keys, self.values = self.store.keyStats()
159         return (self.keys, self.values)
160     
161     def gather(self):
162         """Gather all the statistics for the DHT.
163         
164         @rtype: C{list} of C{dictionary}
165         @return: each dictionary has keys describing the statistic:
166             name, group, desc, tip, and value
167         """
168         self.tableStats()
169         self.dbStats()
170         stats = self._StatsTemplate[:]
171         for stat in stats:
172             val = getattr(self, stat['name'], None)
173             if stat['name'] == 'uptime':
174                 stat['value'] = dattime.now() - self.startTime
175             elif stat['name'] == 'actions':
176                 stat['value'] = deepcopy(actions)
177             elif val is not None:
178                 stat['value'] = val
179                 
180         return stats
181     
182     #{ Called by the transport
183     def sentAction(self, action):
184         """Record that an action was attempted.
185         
186         @param action: the name of the action
187         """
188         act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
189         act[0] += 1
190         
191     def responseAction(self, response, action):
192         """Record that a response to an action was received.
193         
194         @param response: the response
195         @param action: the name of the action
196         @return: the response (for use in deferreds)
197         """
198         act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
199         act[1] += 1
200         return response
201         
202     def failedAction(self, response, action):
203         """Record that a failed response to an action was received.
204         
205         @param response: the response
206         @param action: the name of the action
207         @return: the response (for use in deferreds)
208         """
209         act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
210         act[2] += 1
211         return response
212         
213     def receivedAction(self, action):
214         """Record that an action was received.
215         
216         @param action: the name of the action
217         """
218         self.reachable = True
219         act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
220         act[3] += 1
221     
222     def errorAction(self, action):
223         """Record that a received action resulted in an error.
224         
225         @param action: the name of the action
226         """
227         act = self.actions.setdefault(action, [0, 0, 0, 0, 0])
228         act[4] += 1
229     
230     def sentBytes(self, bytes):
231         """Record that a single packet of some bytes was sent.
232         
233         @param bytes: the number of bytes in the packet
234         """
235         self.upPackets += 1
236         self.upBytes += bytes
237         
238     def receivedBytes(self, bytes):
239         """Record that a single packet of some bytes was received.
240         
241         @param bytes: the number of bytes in the packet
242         """
243         self.downPackets += 1
244         self.downBytes += bytes