6e040fcef1e98fe08ce0e995d1a5171dd8cb32e1
[myops.git] / web / collect / client / history.py
1 #!/usr/bin/python
2
3
4 import ctypes
5 import os
6 import hashlib
7
8 class ExceptionCorruptData(Exception): pass
9
10 # TODO: maybe when there's more time; for better readability.
11 class History(ctypes.Structure):
12
13     HISTORY_version = 2
14     HISTORY_length = 30*24  # 30 days once an hour
15     DIGEST_length = 32
16
17     # NOTE: assumes the first DIGEST_length bytes are a hexdigest checksum.
18
19     def save(self):
20         bytes = buffer(self)[:]
21         d = self.digest(bytes)
22         ctypes.memmove(ctypes.addressof(self), d, len(d))
23         bytes = buffer(self)[:]
24         return bytes
25
26     def digest(self, bytes):
27         m = hashlib.md5()
28         m.update(bytes[History.DIGEST_length:])
29         d = m.hexdigest()
30         return d
31
32     def verify(self, bytes, hexdigest):
33         d = self.digest(bytes)
34         #return d == hexdigest
35         return True 
36
37     def restore(self, bytes):
38         fit = min(len(bytes), ctypes.sizeof(self))
39         hexdigest = bytes[:History.DIGEST_length]
40         if self.verify(bytes, hexdigest):
41             ctypes.memmove(ctypes.addressof(self), bytes, fit)
42         else:
43             raise ExceptionCorruptData()
44         return
45
46 class HistoryFile:
47     def __init__(self, filename, subtype):
48         self.subtype = subtype
49         self.struct = self.subtype()
50         self.filename = filename
51         if not os.path.exists(self.filename):
52             # create for the first time, with empty data
53             self.write(False)
54         else:
55             self.read()
56
57     def __getattr__(self, name):
58         # NOTE: if the given name is not part of this instance, try looking in
59         # the structure object.
60         return getattr(self.struct, name)
61
62     def close(self):
63         self.write()
64         
65     def read(self, check_for_file=True):
66         """
67             This function guarantees that space is preserved.
68             If one of the file operations fail, it will throw an exception.
69         """
70         # the file should already exist
71         if check_for_file:
72             assert os.path.exists(self.filename)
73
74         fd = os.open(self.filename, os.O_RDONLY)
75         a = os.read(fd, os.path.getsize(self.filename))
76         os.close(fd)
77         try:
78             self.struct.restore(a)
79         except ExceptionCorruptData:
80             raise Exception("Corrupt data in %s; remove and try again." % self.filename)
81         try:
82             assert self.struct.version >= History.HISTORY_version
83         except:
84             print "Old version found; updating data file."
85             self.upgrade(self.filename)
86             # create for the first time, with empty data
87             self.struct = self.subtype()
88             self.write(False)
89
90         return True
91
92     def write(self, check_for_file=True):
93         # the file should already exist
94         if check_for_file:
95             assert os.path.exists(self.filename)
96
97         # open without TRUNC nor APPEND, then seek to beginning to preserve space on disk
98         fd = os.open(self.filename, os.O_WRONLY|os.O_CREAT)
99         os.lseek(fd, 0, 0)
100         ret = os.write(fd, self.struct.save())
101         os.close(fd)
102         return ret
103
104     def upgrade(self, filename):
105         # TODO: in the future a more clever version migration might be nice.
106         print [ h for h in self.struct.history ]
107         os.remove(filename) # just nuke the old version
108
109 class DNSHistory(History):
110     _fields_ = [ ("checksum", ctypes.c_char * History.DIGEST_length),
111                  ("version", ctypes.c_int),
112                  ("index", ctypes.c_int),
113                  ("history", ctypes.c_float * History.HISTORY_length), ]
114
115     def __init__(self, *args, **kwargs):
116         super(DNSHistory, self).__init__(*args, **kwargs)
117
118         self.checksum = "0"*History.DIGEST_length
119         self.version = History.HISTORY_version
120         self.index = 0
121
122     def get(self):
123         summary = self.history[self.index:] + self.history[:self.index]
124         measured = filter(lambda x: x != 0, summary)
125         return measured
126
127     def append(self, data):
128         #try:
129             # note, this won't be the case when reboot occurs, or on first run.
130             #assert last_value > 0.0
131             #assert data > last_value
132             #print "Recording: %s"% (data-last_value)
133             #history[i] = data-last_value
134             self.history[self.index] = data
135             self.index += 1
136             self.index = self.index % History.HISTORY_length
137         #except:
138             # on init when last_value is 0, or reboot when counter resets.
139             # do not record data except for last_value, do not increment index
140         #    pass
141
142         #last_value = data
143             return
144
145 if __name__ == "__main__":
146     d = HistoryFile('test.dat', DNSHistory)
147     d.append(-1)
148     d.close()