]> git.ipfire.org Git - collecty.git/blame - collecty/plugins/base.py
plugins: Filenames should contain the ID.
[collecty.git] / collecty / plugins / base.py
CommitLineData
eed405de
MT
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
4ac0cdf0
MT
22import logging
23import os
24import rrdtool
49ce926e 25import threading
4ac0cdf0
MT
26import time
27
4ac0cdf0 28from ..constants import *
eed405de
MT
29from ..i18n import _
30
4be39bf9
MT
31class 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
49ce926e 59class Plugin(threading.Thread):
4ac0cdf0
MT
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
73db5226
MT
69 # Instructions how to create the graph.
70 rrd_graph = None
71
72 # Extra arguments passed to rrdgraph.
73 rrd_graph_args = []
74
4ac0cdf0
MT
75 # The default interval of this plugin.
76 default_interval = 60
77
eed405de 78 def __init__(self, collecty, **kwargs):
49ce926e
MT
79 threading.Thread.__init__(self, name=self.description)
80 self.daemon = True
81
eed405de
MT
82 self.collecty = collecty
83
4ac0cdf0
MT
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
eed405de 92
eed405de
MT
93 self.data = []
94
269f74cd
MT
95 # Run some custom initialization.
96 self.init(**kwargs)
97
4ac0cdf0 98 # Create the database file.
eed405de 99 self.create()
4ac0cdf0 100
4be39bf9
MT
101 # Keepalive options
102 self.running = True
103 self.timer = Timer(self.interval)
104
75b3f22e 105 self.log.info(_("Successfully initialized (%s).") % self.id)
eed405de
MT
106
107 def __repr__(self):
4ac0cdf0 108 return "<Plugin %s>" % self.name
eed405de
MT
109
110 def __str__(self):
4ac0cdf0
MT
111 return "Plugin %s %s" % (self.name, self.file)
112
73db5226
MT
113 @property
114 def id(self):
115 """
116 A unique ID of the plugin instance.
117 """
118 return self.name
119
4ac0cdf0
MT
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 """
75b3f22e 136 return os.path.join(DATABASE_DIR, "%s.rrd" % self.id)
eed405de 137
4ac0cdf0
MT
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
eed405de 145
4ac0cdf0
MT
146 dirname = os.path.dirname(self.file)
147 if not os.path.exists(dirname):
148 os.makedirs(dirname)
eed405de 149
4ac0cdf0 150 rrdtool.create(self.file, *self.rrd_schema)
eed405de 151
4ac0cdf0 152 self.log.debug(_("Created RRD file %s.") % self.file)
eed405de 153
4ac0cdf0
MT
154 def info(self):
155 return rrdtool.info(self.file)
eed405de 156
4ac0cdf0
MT
157 ### Basic methods
158
269f74cd 159 def init(self, **kwargs):
4ac0cdf0
MT
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
49ce926e 179 self.log.debug(_("Submitting data to database. %d entries.") % len(self.data))
4ac0cdf0
MT
180 rrdtool.update(self.file, *self.data)
181 self.data = []
eed405de 182
4dc6b0c9 183 def _read(self, *args, **kwargs):
4ac0cdf0
MT
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
4dc6b0c9 194 def _submit(self, *args, **kwargs):
4ac0cdf0
MT
195 """
196 This method catches errors from the submit() method and logs them.
197 """
198 try:
199 return self.submit(*args, **kwargs)
eed405de 200
4ac0cdf0
MT
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."))
eed405de 207
4ac0cdf0 208 while self.running:
4be39bf9
MT
209 # Reset the timer.
210 self.timer.reset()
211
212 # Wait until the timer has successfully elapsed.
213 if self.timer.wait():
4ac0cdf0 214 self.log.debug(_("Collecting..."))
4dc6b0c9 215 self._read()
4ac0cdf0 216
4dc6b0c9 217 self._submit()
4ac0cdf0
MT
218 self.log.debug(_("Stopped."))
219
220 def shutdown(self):
221 self.log.debug(_("Received shutdown signal."))
222 self.running = False
223
4be39bf9
MT
224 # Kill any running timers.
225 if self.timer:
226 self.timer.cancel()
227
4ac0cdf0
MT
228 @property
229 def now(self):
230 """
231 Returns the current timestamp in the UNIX timestamp format (UTC).
232 """
233 return int(time.time())
eed405de 234
73db5226
MT
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
eed405de 244
73db5226
MT
245 intervals = {
246 None : "-3h",
247 "hour" : "-1h",
248 "day" : "-25h",
249 "week" : "-360h",
250 "year" : "-365d",
251 }
eed405de
MT
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 }
73db5226 260 for item in self.rrd_graph:
eed405de
MT
261 try:
262 args.append(item % info)
263 except TypeError:
264 args.append(item)
265
73db5226 266 rrdtool.graph(file, *args)