X-Git-Url: http://git.onelab.eu/?p=myops.git;a=blobdiff_plain;f=web%2Fcollect%2Fclient%2Fhistory.py;fp=web%2Fcollect%2Fclient%2Fhistory.py;h=6e040fcef1e98fe08ce0e995d1a5171dd8cb32e1;hp=0000000000000000000000000000000000000000;hb=3ebc599db4bc491de0dd4195b2e6dc858dbd3d22;hpb=2c16b9ee5acc1b619824df8d4e4fb57c6a02dee7 diff --git a/web/collect/client/history.py b/web/collect/client/history.py new file mode 100755 index 0000000..6e040fc --- /dev/null +++ b/web/collect/client/history.py @@ -0,0 +1,148 @@ +#!/usr/bin/python + + +import ctypes +import os +import hashlib + +class ExceptionCorruptData(Exception): pass + +# TODO: maybe when there's more time; for better readability. +class History(ctypes.Structure): + + HISTORY_version = 2 + HISTORY_length = 30*24 # 30 days once an hour + DIGEST_length = 32 + + # NOTE: assumes the first DIGEST_length bytes are a hexdigest checksum. + + def save(self): + bytes = buffer(self)[:] + d = self.digest(bytes) + ctypes.memmove(ctypes.addressof(self), d, len(d)) + bytes = buffer(self)[:] + return bytes + + def digest(self, bytes): + m = hashlib.md5() + m.update(bytes[History.DIGEST_length:]) + d = m.hexdigest() + return d + + def verify(self, bytes, hexdigest): + d = self.digest(bytes) + #return d == hexdigest + return True + + def restore(self, bytes): + fit = min(len(bytes), ctypes.sizeof(self)) + hexdigest = bytes[:History.DIGEST_length] + if self.verify(bytes, hexdigest): + ctypes.memmove(ctypes.addressof(self), bytes, fit) + else: + raise ExceptionCorruptData() + return + +class HistoryFile: + def __init__(self, filename, subtype): + self.subtype = subtype + self.struct = self.subtype() + self.filename = filename + if not os.path.exists(self.filename): + # create for the first time, with empty data + self.write(False) + else: + self.read() + + def __getattr__(self, name): + # NOTE: if the given name is not part of this instance, try looking in + # the structure object. + return getattr(self.struct, name) + + def close(self): + self.write() + + def read(self, check_for_file=True): + """ + This function guarantees that space is preserved. + If one of the file operations fail, it will throw an exception. + """ + # the file should already exist + if check_for_file: + assert os.path.exists(self.filename) + + fd = os.open(self.filename, os.O_RDONLY) + a = os.read(fd, os.path.getsize(self.filename)) + os.close(fd) + try: + self.struct.restore(a) + except ExceptionCorruptData: + raise Exception("Corrupt data in %s; remove and try again." % self.filename) + try: + assert self.struct.version >= History.HISTORY_version + except: + print "Old version found; updating data file." + self.upgrade(self.filename) + # create for the first time, with empty data + self.struct = self.subtype() + self.write(False) + + return True + + def write(self, check_for_file=True): + # the file should already exist + if check_for_file: + assert os.path.exists(self.filename) + + # open without TRUNC nor APPEND, then seek to beginning to preserve space on disk + fd = os.open(self.filename, os.O_WRONLY|os.O_CREAT) + os.lseek(fd, 0, 0) + ret = os.write(fd, self.struct.save()) + os.close(fd) + return ret + + def upgrade(self, filename): + # TODO: in the future a more clever version migration might be nice. + print [ h for h in self.struct.history ] + os.remove(filename) # just nuke the old version + +class DNSHistory(History): + _fields_ = [ ("checksum", ctypes.c_char * History.DIGEST_length), + ("version", ctypes.c_int), + ("index", ctypes.c_int), + ("history", ctypes.c_float * History.HISTORY_length), ] + + def __init__(self, *args, **kwargs): + super(DNSHistory, self).__init__(*args, **kwargs) + + self.checksum = "0"*History.DIGEST_length + self.version = History.HISTORY_version + self.index = 0 + + def get(self): + summary = self.history[self.index:] + self.history[:self.index] + measured = filter(lambda x: x != 0, summary) + return measured + + def append(self, data): + #try: + # note, this won't be the case when reboot occurs, or on first run. + #assert last_value > 0.0 + #assert data > last_value + #print "Recording: %s"% (data-last_value) + #history[i] = data-last_value + self.history[self.index] = data + self.index += 1 + self.index = self.index % History.HISTORY_length + #except: + # on init when last_value is 0, or reboot when counter resets. + # do not record data except for last_value, do not increment index + # pass + + #last_value = data + return + +if __name__ == "__main__": + d = HistoryFile('test.dat', DNSHistory) + d.append(-1) + d.close()