summaryrefslogtreecommitdiffstats
path: root/src/lib/Server/Plugins/Statistics.py
blob: b7dc61179950929704875a453d99248de6473565 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
'''This file manages the statistics collected by the BCFG2 Server'''
__revision__ = '$Revision$'

from lxml.etree import XML, SubElement, Element, XMLSyntaxError
from time import asctime, localtime, time, strptime, mktime

import logging, lxml.etree, os

import Bcfg2.Server.Plugin

class StatisticsStore(object):
    '''Manages the memory and file copy of statistics collected about client runs'''
    __min_write_delay__ = 0

    def __init__(self, filename):
        self.filename = filename
        self.element = Element('Dummy')
        self.dirty = 0
        self.lastwrite = 0
        self.logger = logging.getLogger('Bcfg2.Server.Statistics')
        self.ReadFromFile()

    def WriteBack(self, force=0):
        '''Write statistics changes back to persistent store'''
        if (self.dirty and (self.lastwrite + self.__min_write_delay__ <= time()) ) \
                or force:
            #syslog(LOG_INFO, "Statistics: Updated statistics.xml")
            try:
                fout = open(self.filename + '.new', 'w')
            except IOError, ioerr:
                self.logger.error("Failed to open %s for writing: %s" % (self.filename + '.new', ioerr))
            else:
                fout.write(lxml.etree.tostring(self.element, encoding='UTF-8', xml_declaration=True))
                fout.close()
                os.rename(self.filename + '.new', self.filename)
                self.dirty = 0
                self.lastwrite = time()

    def ReadFromFile(self):
        '''Reads current state regarding statistics'''
        try:
            fin = open(self.filename, 'r')
            data = fin.read()
            fin.close()
            self.element = XML(data)
            self.dirty = 0
            #syslog(LOG_INFO, "Statistics: Read in statistics.xml")
        except (IOError, XMLSyntaxError):
            self.logger.error("Creating new statistics file %s"%(self.filename))
            self.element = Element('ConfigStatistics')
            self.WriteBack()
            self.dirty = 0

    def updateStats(self, xml, client):
        '''Updates the statistics of a current node with new data'''

        # Current policy: 
        # - Keep anything less than 24 hours old
        #   - Keep latest clean run for clean nodes
        #   - Keep latest clean and dirty run for dirty nodes
        newstat =  xml.find('Statistics')

        if newstat.get('state') == 'clean':
            node_dirty = 0
        else:
            node_dirty = 1

        # Find correct node entry in stats data
        # The following list comprehension should be guarenteed to return at
        # most one result
        nodes = [elem for elem in self.element.findall('Node') if elem.get('name') == client]
        nummatch = len(nodes)
        if nummatch == 0:
            # Create an entry for this node
            node = SubElement(self.element, 'Node', name=client)
        elif nummatch == 1 and not node_dirty:
            # Delete old instance
            node = nodes[0]
            [node.remove(elem) for elem in node.findall('Statistics') if self.isOlderThan24h(elem.get('time'))]
        elif nummatch == 1 and node_dirty:
            # Delete old dirty statistics entry
            node = nodes[0]
            [node.remove(elem) for elem in node.findall('Statistics') if (elem.get('state') == 'dirty' and self.isOlderThan24h(elem.get('time')))]
        else:
            # Shouldn't be reached
            self.logger.error("Duplicate node entry for %s"%(client))

        # Set current time for stats
        newstat.set('time', asctime(localtime()))

        # Add statistic
        node.append(newstat)

        # Set dirty
        self.dirty = 1
        self.WriteBack()


    def isOlderThan24h(self, testTime):
        '''Helper function to determine if <time> string is older than 24 hours'''
        now = time()
        utime = mktime(strptime(testTime))
        secondsPerDay = 60*60*24

        return (now-utime) > secondsPerDay

class Statistics(Bcfg2.Server.Plugin.StatisticsPlugin):
    __name__ = 'Statistics'
    __version__ = '$Id$'

    def __init__(self, _, datastore):
        fpath = "%s/etc/statistics.xml" % datastore
        self.data = StatisticsStore(fpath)

    def StoreStatistics(self, client, xdata):
        self.data.updateStats(xdata, client.hostname)

    def WriteBack(self):
        self.data.WriteBack()

    def FindCurrent(self, client):
        rt = self.data.element.xpath('//Node[@name="%s"]' % client)[0]
        maxtime = max([strptime(stat.get('time')) for stat in rt.findall('Statistics')])
        return [stat for stat in rt.findall('Statistics') if strptime(stat.get('time')) == maxtime][0]
    
    def GetExtra(self, client):
        return [(entry.tag, entry.get('name')) for entry in self.FindCurrent(client).xpath('.//Extra/*')]