]> git.ipfire.org Git - collecty.git/blob - collecty/plugins/base.py
plugins: Restructure the plugin concept.
[collecty.git] / collecty / plugins / base.py
1 #!/usr/bin/python
2 ###############################################################################
3 # #
4 # collecty - A system statistics collection daemon for IPFire #
5 # Copyright (C) 2012 IPFire development team #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 import logging
23 import os
24 import rrdtool
25 import time
26
27 from threading import Thread
28
29 from ..constants import *
30 from ..i18n import _
31
32 class Plugin(Thread):
33 # The name of this plugin.
34 name = None
35
36 # A description for this plugin.
37 description = None
38
39 # The schema of the RRD database.
40 rrd_schema = None
41
42 # The default interval of this plugin.
43 default_interval = 60
44
45 def __init__(self, collecty, **kwargs):
46 Thread.__init__(self)
47 self.collecty = collecty
48
49 # Check if this plugin was configured correctly.
50 assert self.name, "Name of the plugin is not set: %s" % self.name
51 assert self.description, "Description of the plugin is not set: %s" % self.description
52 assert self.rrd_schema
53
54 # Initialize the logger.
55 self.log = logging.getLogger("collecty.plugins.%s" % self.name)
56 self.log.propagate = 1
57
58 # Keepalive options
59 self.heartbeat = 2
60 self.running = True
61
62 self.data = []
63
64 # Create the database file.
65 self.create()
66
67 # Run some custom initialization.
68 self.init()
69
70 self.log.info(_("Successfully initialized."))
71
72 def __repr__(self):
73 return "<Plugin %s>" % self.name
74
75 def __str__(self):
76 return "Plugin %s %s" % (self.name, self.file)
77
78 @property
79 def interval(self):
80 """
81 Returns the interval in milliseconds, when the read method
82 should be called again.
83 """
84 # XXX read this from the settings
85
86 # Otherwise return the default.
87 return self.default_interval
88
89 @property
90 def file(self):
91 """
92 The absolute path to the RRD file of this plugin.
93 """
94 return os.path.join(DATABASE_DIR, "%s.rrd" % self.name)
95
96 def create(self):
97 """
98 Creates an empty RRD file with the desired data structures.
99 """
100 # Skip if the file does already exist.
101 if os.path.exists(self.file):
102 return
103
104 dirname = os.path.dirname(self.file)
105 if not os.path.exists(dirname):
106 os.makedirs(dirname)
107
108 rrdtool.create(self.file, *self.rrd_schema)
109
110 self.log.debug(_("Created RRD file %s.") % self.file)
111
112 def info(self):
113 return rrdtool.info(self.file)
114
115 ### Basic methods
116
117 def init(self):
118 """
119 Do some custom initialization stuff here.
120 """
121 pass
122
123 def read(self):
124 """
125 Gathers the statistical data, this plugin collects.
126 """
127 raise NotImplementedError
128
129 def submit(self):
130 """
131 Flushes the read data to disk.
132 """
133 # Do nothing in case there is no data to submit.
134 if not self.data:
135 return
136
137 self.collecty.debug(_("Saving data from %s...") % self)
138 rrdtool.update(self.file, *self.data)
139 self.data = []
140
141 def __read(self, *args, **kwargs):
142 """
143 This method catches errors from the read() method and logs them.
144 """
145 try:
146 return self.read(*args, **kwargs)
147
148 # Catch any exceptions, so collecty does not crash.
149 except Exception, e:
150 self.log.critical(_("Unhandled exception in read()!"), exc_info=True)
151
152 def __submit(self, *args, **kwargs):
153 """
154 This method catches errors from the submit() method and logs them.
155 """
156 try:
157 return self.submit(*args, **kwargs)
158
159 # Catch any exceptions, so collecty does not crash.
160 except Exception, e:
161 self.log.critical(_("Unhandled exception in submit()!"), exc_info=True)
162
163 def run(self):
164 self.log.debug(_("Started."))
165
166 counter = 0
167 while self.running:
168 if counter == 0:
169 self.log.debug(_("Collecting..."))
170 self.__read()
171
172 self.log.debug(_("Sleeping for %.4fs.") % self.interval)
173
174 counter = self.interval / self.heartbeat
175
176 time.sleep(self.heartbeat)
177 counter -= 1
178
179 self.__submit()
180 self.log.debug(_("Stopped."))
181
182 def shutdown(self):
183 self.log.debug(_("Received shutdown signal."))
184 self.running = False
185
186 @property
187 def now(self):
188 """
189 Returns the current timestamp in the UNIX timestamp format (UTC).
190 """
191 return int(time.time())
192
193 def graph(self, file, interval=None):
194 args = [ "--imgformat", "PNG",
195 "-w", "580", # Width of the graph
196 "-h", "240", # Height of the graph
197 "--interlaced", "--slope-mode", ]
198
199 intervals = { None : "-3h",
200 "hour" : "-1h",
201 "day" : "-25h",
202 "week" : "-360h" }
203
204 args.append("--start")
205 if intervals.has_key(interval):
206 args.append(intervals[interval])
207 else:
208 args.append(interval)
209
210 info = { "file" : self.file }
211 for item in self._graph:
212 try:
213 args.append(item % info)
214 except TypeError:
215 args.append(item)
216
217 rrdtool.graph(file, *args)