Add statistics reporting to the main program (untested).
[quix0rs-apt-p2p.git] / apt_p2p / stats.py
1
2 """Store statistics for the Apt-P2P downloader."""
3
4 from datetime import datetime, timedelta
5 from StringIO import StringIO
6
7 from util import byte_format
8
9 class StatsLogger:
10     """Store the statistics for the Khashmir DHT.
11     
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, db):
35         """Initialize the statistics.
36         
37         @type store: L{db.DB}
38         @param store: the database for the Apt-P2P downloader
39         """
40         # Database
41         self.db = db
42         self.lastDBUpdate = datetime.now()
43         self.hashes, self.files = self.db.dbStats()
44         
45         # Transport
46         self.mirrorDown = 0L
47         self.peerDown = 0L
48         self.peerUp = 0L
49         
50         # Transport All-Time
51         stats = self.db.getStats()
52         self.mirrorAllDown = long(stats.get('mirror_down', 0L))
53         self.peerAllDown = long(stats.get('peer_down', 0L))
54         self.peerAllUp = long(stats.get('peer_up', 0L))
55         
56     def save(self):
57         """Save the persistent statistics to the DB."""
58         stats = {'mirror_down': self.mirrorAllDown,
59                  'peer_down': self.peerAllDown,
60                  'peer_up': self.peerAllUp,
61                  }
62         self.db.saveStats(stats)
63     
64     def dbStats(self):
65         """Collect some statistics about the database.
66         
67         @rtype: (C{int}, C{int})
68         @return: the number of keys and values in the database
69         """
70         if datetime.now() - self.lastDBUpdate > timedelta(minutes = 1):
71             self.lastDBUpdate = datetime.now()
72             self.hashes, self.files = self.db.keyStats()
73         return (self.hashes, self.files)
74     
75     def formatHTML(self, contactAddress):
76         """Gather statistics for the DHT and format them for display in a browser.
77         
78         @param contactAddress: the external IP address in use
79         @rtype: C{string}
80         @return: the stats, formatted for display in the body of an HTML page
81         """
82         self.dbStats()
83
84         out = StringIO()
85         out.write('<h2>Downloader Statistics</h2>\n')
86         out.write("<table border='0' cellspacing='20px'>\n<tr>\n")
87         out.write('<td>\n')
88
89         # General
90         out.write("<table border='1' cellpadding='4px'>\n")
91         out.write("<tr><th><h3>General</h3></th><th>Value</th></tr>\n")
92         out.write("<tr title='Contact address for this peer'><td>Contact</td><td>" + str(contactAdress) + '</td></tr>\n')
93         out.write("</table>\n")
94         out.write('</td><td>\n')
95         
96         # Database
97         out.write("<table border='1' cellpadding='4px'>\n")
98         out.write("<tr><th><h3>Database</h3></th><th>Value</th></tr>\n")
99         out.write("<tr title='Number of distinct files in the database'><td>Distinct Files</td><td>" + str(self.hashes) + '</td></tr>\n')
100         out.write("<tr title='Total number of files being shared'><td>Total Files</td><td>" + str(self.files) + '</td></tr>\n')
101         out.write("</table>\n")
102         out.write("</td></tr><tr><td colspan='3'>\n")
103         
104         # Transport
105         out.write("<table border='1' cellpadding='4px'>\n")
106         out.write("<tr><th><h3>Transport</h3></th><th>Mirror Downloads</th><th>Peer Downloads</th><th>Peer Uploads</th></tr>\n")
107         out.write("<tr><td title='Since the program was last restarted'>This Session</td>")
108         out.write("<td title='Amount downloaded from mirrors'>" + byte_format(self.mirrorDown) + '</td>')
109         out.write("<td title='Amount downloaded from peers'>" + byte_format(self.peerDown) + '</td>')
110         out.write("<td title='Amount uploaded to peers'>" + byte_format(self.peerUp) + '</td></tr>')
111         out.write("<tr><td title='Since the program was last restarted'>Session Ratio</td>")
112         out.write("<td title='Percent of download from mirrors'>%0.2f%%</td>" %
113                   (float(self.mirrorDown) / float(self.mirrorDown + self.peerDown), ))
114         out.write("<td title='Percent of download from peers'>%0.2f%%</td>" %
115                   (float(self.peerDown) / float(self.mirrorDown + self.peerDown), ))
116         out.write("<td title='Percent uploaded to peers compared with downloaded from peers'>%0.2f%%</td></tr>" %
117                   (float(self.peerUp) / float(self.peerDown), ))
118         out.write("<tr><td title='Since the program was installed'>All-Time</td>")
119         out.write("<td title='Amount downloaded from mirrors'>" + byte_format(self.mirrorAllDown) + '</td>')
120         out.write("<td title='Amount downloaded from peers'>" + byte_format(self.peerAllDown) + '</td>')
121         out.write("<td title='Amount uploaded to peers'>" + byte_format(self.peerAllUp) + '</td></tr>')
122         out.write("<tr><td title='Since the program was installed'>All-Time Ratio</td>")
123         out.write("<td title='Percent of download from mirrors'>%0.2f%%</td>" %
124                   (float(self.mirrorAllDown) / float(self.mirrorAllDown + self.peerAllDown), ))
125         out.write("<td title='Percent of download from peers'>%0.2f%%</td>" %
126                   (float(self.peerAllDown) / float(self.mirrorAllDown + self.peerAllDown), ))
127         out.write("<td title='Percent uploaded to peers compared with downloaded from peers'>%0.2f%%</td></tr>" %
128                   (float(self.peerAllUp) / float(self.peerAllDown), ))
129         out.write("</table>\n")
130         out.write("</td></tr>\n")
131         out.write("</table>\n")
132         
133         return out.getvalue()
134
135     #{ Transport
136     def sentBytes(self, bytes):
137         """Record that some bytes were sent.
138         
139         @param bytes: the number of bytes sent
140         """
141         self.peerUp += bytes
142         self.peerAllUp += bytes
143         
144     def receivedBytes(self, bytes, mirror = False):
145         """Record that some bytes were received.
146         
147         @param bytes: the number of bytes received
148         @param mirror: whether the bytes were sent to a mirror
149         """
150         if mirror:
151             self.mirrorDown += bytes
152             self.mirrorAllDown += bytes
153         else:
154             self.peerDown += bytes
155             self.peerAllDown += bytes