]> git.ipfire.org Git - collecty.git/blob - collecty/plugins/base.py
Replace those complicated wait construct by a efficient Timer class.
[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 # The default interval of this plugin.
70 default_interval = 60
71
72 def __init__(self, collecty, **kwargs):
73 threading.Thread.__init__(self, name=self.description)
74 self.daemon = True
75
76 self.collecty = collecty
77
78 # Check if this plugin was configured correctly.
79 assert self.name, "Name of the plugin is not set: %s" % self.name
80 assert self.description, "Description of the plugin is not set: %s" % self.description
81 assert self.rrd_schema
82
83 # Initialize the logger.
84 self.log = logging.getLogger("collecty.plugins.%s" % self.name)
85 self.log.propagate = 1
86
87 self.data = []
88
89 # Create the database file.
90 self.create()
91
92 # Run some custom initialization.
93 self.init()
94
95 # Keepalive options
96 self.running = True
97 self.timer = Timer(self.interval)
98
99 self.log.info(_("Successfully initialized."))
100
101 def __repr__(self):
102 return "<Plugin %s>" % self.name
103
104 def __str__(self):
105 return "Plugin %s %s" % (self.name, self.file)
106
107 @property
108 def interval(self):
109 """
110 Returns the interval in milliseconds, when the read method
111 should be called again.
112 """
113 # XXX read this from the settings
114
115 # Otherwise return the default.
116 return self.default_interval
117
118 @property
119 def file(self):
120 """
121 The absolute path to the RRD file of this plugin.
122 """
123 return os.path.join(DATABASE_DIR, "%s.rrd" % self.name)
124
125 def create(self):
126 """
127 Creates an empty RRD file with the desired data structures.
128 """
129 # Skip if the file does already exist.
130 if os.path.exists(self.file):
131 return
132
133 dirname = os.path.dirname(self.file)
134 if not os.path.exists(dirname):
135 os.makedirs(dirname)
136
137 rrdtool.create(self.file, *self.rrd_schema)
138
139 self.log.debug(_("Created RRD file %s.") % self.file)
140
141 def info(self):
142 return rrdtool.info(self.file)
143
144 ### Basic methods
145
146 def init(self):
147 """
148 Do some custom initialization stuff here.
149 """
150 pass
151
152 def read(self):
153 """
154 Gathers the statistical data, this plugin collects.
155 """
156 raise NotImplementedError
157
158 def submit(self):
159 """
160 Flushes the read data to disk.
161 """
162 # Do nothing in case there is no data to submit.
163 if not self.data:
164 return
165
166 self.log.debug(_("Submitting data to database. %d entries.") % len(self.data))
167 rrdtool.update(self.file, *self.data)
168 self.data = []
169
170 def _read(self, *args, **kwargs):
171 """
172 This method catches errors from the read() method and logs them.
173 """
174 try:
175 return self.read(*args, **kwargs)
176
177 # Catch any exceptions, so collecty does not crash.
178 except Exception, e:
179 self.log.critical(_("Unhandled exception in read()!"), exc_info=True)
180
181 def _submit(self, *args, **kwargs):
182 """
183 This method catches errors from the submit() method and logs them.
184 """
185 try:
186 return self.submit(*args, **kwargs)
187
188 # Catch any exceptions, so collecty does not crash.
189 except Exception, e:
190 self.log.critical(_("Unhandled exception in submit()!"), exc_info=True)
191
192 def run(self):
193 self.log.debug(_("Started."))
194
195 while self.running:
196 # Reset the timer.
197 self.timer.reset()
198
199 # Wait until the timer has successfully elapsed.
200 if self.timer.wait():
201 self.log.debug(_("Collecting..."))
202 self._read()
203
204 self._submit()
205 self.log.debug(_("Stopped."))
206
207 def shutdown(self):
208 self.log.debug(_("Received shutdown signal."))
209 self.running = False
210
211 # Kill any running timers.
212 if self.timer:
213 self.timer.cancel()
214
215 @property
216 def now(self):
217 """
218 Returns the current timestamp in the UNIX timestamp format (UTC).
219 """
220 return int(time.time())
221
222 def graph(self, file, interval=None):
223 args = [ "--imgformat", "PNG",
224 "-w", "580", # Width of the graph
225 "-h", "240", # Height of the graph
226 "--interlaced", "--slope-mode", ]
227
228 intervals = { None : "-3h",
229 "hour" : "-1h",
230 "day" : "-25h",
231 "week" : "-360h" }
232
233 args.append("--start")
234 if intervals.has_key(interval):
235 args.append(intervals[interval])
236 else:
237 args.append(interval)
238
239 info = { "file" : self.file }
240 for item in self._graph:
241 try:
242 args.append(item % info)
243 except TypeError:
244 args.append(item)
245
246 rrdtool.graph(file, *args)