]> git.ipfire.org Git - collecty.git/blob - collecty/plugins/base.py
plugins: Filenames should contain the ID.
[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 threading
26 import time
27
28 from ..constants import *
29 from ..i18n import _
30
31 class Timer(object):
32 def __init__(self, timeout, heartbeat=1):
33 self.timeout = timeout
34 self.heartbeat = heartbeat
35
36 self.reset()
37
38 def reset(self):
39 # Save start time.
40 self.start = time.time()
41
42 # Has this timer been killed?
43 self.killed = False
44
45 @property
46 def elapsed(self):
47 return time.time() - self.start
48
49 def cancel(self):
50 self.killed = True
51
52 def wait(self):
53 while self.elapsed < self.timeout and not self.killed:
54 time.sleep(self.heartbeat)
55
56 return self.elapsed > self.timeout
57
58
59 class Plugin(threading.Thread):
60 # The name of this plugin.
61 name = None
62
63 # A description for this plugin.
64 description = None
65
66 # The schema of the RRD database.
67 rrd_schema = None
68
69 # Instructions how to create the graph.
70 rrd_graph = None
71
72 # Extra arguments passed to rrdgraph.
73 rrd_graph_args = []
74
75 # The default interval of this plugin.
76 default_interval = 60
77
78 def __init__(self, collecty, **kwargs):
79 threading.Thread.__init__(self, name=self.description)
80 self.daemon = True
81
82 self.collecty = collecty
83
84 # Check if this plugin was configured correctly.
85 assert self.name, "Name of the plugin is not set: %s" % self.name
86 assert self.description, "Description of the plugin is not set: %s" % self.description
87 assert self.rrd_schema
88
89 # Initialize the logger.
90 self.log = logging.getLogger("collecty.plugins.%s" % self.name)
91 self.log.propagate = 1
92
93 self.data = []
94
95 # Run some custom initialization.
96 self.init(**kwargs)
97
98 # Create the database file.
99 self.create()
100
101 # Keepalive options
102 self.running = True
103 self.timer = Timer(self.interval)
104
105 self.log.info(_("Successfully initialized (%s).") % self.id)
106
107 def __repr__(self):
108 return "<Plugin %s>" % self.name
109
110 def __str__(self):
111 return "Plugin %s %s" % (self.name, self.file)
112
113 @property
114 def id(self):
115 """
116 A unique ID of the plugin instance.
117 """
118 return self.name
119
120 @property
121 def interval(self):
122 """
123 Returns the interval in milliseconds, when the read method
124 should be called again.
125 """
126 # XXX read this from the settings
127
128 # Otherwise return the default.
129 return self.default_interval
130
131 @property
132 def file(self):
133 """
134 The absolute path to the RRD file of this plugin.
135 """
136 return os.path.join(DATABASE_DIR, "%s.rrd" % self.id)
137
138 def create(self):
139 """
140 Creates an empty RRD file with the desired data structures.
141 """
142 # Skip if the file does already exist.
143 if os.path.exists(self.file):
144 return
145
146 dirname = os.path.dirname(self.file)
147 if not os.path.exists(dirname):
148 os.makedirs(dirname)
149
150 rrdtool.create(self.file, *self.rrd_schema)
151
152 self.log.debug(_("Created RRD file %s.") % self.file)
153
154 def info(self):
155 return rrdtool.info(self.file)
156
157 ### Basic methods
158
159 def init(self, **kwargs):
160 """
161 Do some custom initialization stuff here.
162 """
163 pass
164
165 def read(self):
166 """
167 Gathers the statistical data, this plugin collects.
168 """
169 raise NotImplementedError
170
171 def submit(self):
172 """
173 Flushes the read data to disk.
174 """
175 # Do nothing in case there is no data to submit.
176 if not self.data:
177 return
178
179 self.log.debug(_("Submitting data to database. %d entries.") % len(self.data))
180 rrdtool.update(self.file, *self.data)
181 self.data = []
182
183 def _read(self, *args, **kwargs):
184 """
185 This method catches errors from the read() method and logs them.
186 """
187 try:
188 return self.read(*args, **kwargs)
189
190 # Catch any exceptions, so collecty does not crash.
191 except Exception, e:
192 self.log.critical(_("Unhandled exception in read()!"), exc_info=True)
193
194 def _submit(self, *args, **kwargs):
195 """
196 This method catches errors from the submit() method and logs them.
197 """
198 try:
199 return self.submit(*args, **kwargs)
200
201 # Catch any exceptions, so collecty does not crash.
202 except Exception, e:
203 self.log.critical(_("Unhandled exception in submit()!"), exc_info=True)
204
205 def run(self):
206 self.log.debug(_("Started."))
207
208 while self.running:
209 # Reset the timer.
210 self.timer.reset()
211
212 # Wait until the timer has successfully elapsed.
213 if self.timer.wait():
214 self.log.debug(_("Collecting..."))
215 self._read()
216
217 self._submit()
218 self.log.debug(_("Stopped."))
219
220 def shutdown(self):
221 self.log.debug(_("Received shutdown signal."))
222 self.running = False
223
224 # Kill any running timers.
225 if self.timer:
226 self.timer.cancel()
227
228 @property
229 def now(self):
230 """
231 Returns the current timestamp in the UNIX timestamp format (UTC).
232 """
233 return int(time.time())
234
235 def graph(self, file, interval=None,
236 width=GRAPH_DEFAULT_WIDTH, height=GRAPH_DEFAULT_HEIGHT):
237
238 args = [
239 "--width", "%d" % width,
240 "--height", "%d" % height,
241 ]
242 args += self.collecty.graph_default_arguments
243 args += self.rrd_graph_args
244
245 intervals = {
246 None : "-3h",
247 "hour" : "-1h",
248 "day" : "-25h",
249 "week" : "-360h",
250 "year" : "-365d",
251 }
252
253 args.append("--start")
254 if intervals.has_key(interval):
255 args.append(intervals[interval])
256 else:
257 args.append(interval)
258
259 info = { "file" : self.file }
260 for item in self.rrd_graph:
261 try:
262 args.append(item % info)
263 except TypeError:
264 args.append(item)
265
266 rrdtool.graph(file, *args)