]>
git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/base.py
2 ###############################################################################
4 # collecty - A system statistics collection daemon for IPFire #
5 # Copyright (C) 2012 IPFire development team #
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. #
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. #
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/>. #
20 ###############################################################################
32 from ..constants
import *
36 def __init__(self
, timeout
, heartbeat
=1):
37 self
.timeout
= timeout
38 self
.heartbeat
= heartbeat
44 def reset(self
, delay
=0):
46 self
.start
= time
.time()
50 # Has this timer been killed?
55 return time
.time() - self
.start
- self
.delay
61 while self
.elapsed
< self
.timeout
and not self
.killed
:
62 time
.sleep(self
.heartbeat
)
64 return self
.elapsed
> self
.timeout
67 class PluginRegistration(type):
70 def __init__(plugin
, name
, bases
, dict):
71 type.__init
__(plugin
, name
, bases
, dict)
73 # The main class from which is inherited is not registered
78 if not all((plugin
.name
, plugin
.description
)):
79 raise RuntimeError(_("Plugin is not properly configured: %s") % plugin
)
81 PluginRegistration
.plugins
[plugin
.name
] = plugin
86 Returns a list with all automatically registered plugins.
88 return PluginRegistration
.plugins
.values()
90 class Plugin(object, metaclass
=PluginRegistration
):
91 # The name of this plugin.
94 # A description for this plugin.
97 # Templates which can be used to generate a graph out of
98 # the data from this data source.
101 # The default interval for all plugins
104 def __init__(self
, collecty
, **kwargs
):
105 self
.collecty
= collecty
107 # Check if this plugin was configured correctly.
108 assert self
.name
, "Name of the plugin is not set: %s" % self
.name
109 assert self
.description
, "Description of the plugin is not set: %s" % self
.description
111 # Initialize the logger.
112 self
.log
= logging
.getLogger("collecty.plugins.%s" % self
.name
)
113 self
.log
.propagate
= 1
117 # Run some custom initialization.
120 self
.log
.debug(_("Successfully initialized %s") % self
.__class
__.__name
__)
125 Returns the name of the sub directory in which all RRD files
126 for this plugin should be stored in.
132 def init(self
, **kwargs
):
134 Do some custom initialization stuff here.
140 Gathers the statistical data, this plugin collects.
142 time_start
= time
.time()
144 # Run through all objects of this plugin and call the collect method.
145 for o
in self
.objects
:
146 now
= datetime
.datetime
.utcnow()
150 if isinstance(result
, tuple) or isinstance(result
, list):
151 result
= ":".join(("%s" % e
for e
in result
))
153 self
.log
.warning(_("Unhandled exception in %s.collect()") % o
, exc_info
=True)
157 self
.log
.warning(_("Received empty result: %s") % o
)
160 self
.log
.debug(_("Collected %s: %s") % (o
, result
))
162 # Add the object to the write queue so that the data is written
163 # to the databases later.
164 self
.collecty
.write_queue
.add(o
, now
, result
)
166 # Returns the time this function took to complete.
167 delay
= time
.time() - time_start
169 # Log some warning when a collect method takes too long to return some data
171 self
.log
.warning(_("A worker thread was stalled for %.4fs") % delay
)
173 def get_object(self
, id):
174 for object in self
.objects
:
175 if not object.id == id:
180 def get_template(self
, template_name
, object_id
):
181 for template
in self
.templates
:
182 if not template
.name
== template_name
:
185 return template(self
, object_id
)
187 def generate_graph(self
, template_name
, object_id
="default", **kwargs
):
188 template
= self
.get_template(template_name
, object_id
=object_id
)
190 raise RuntimeError("Could not find template %s" % template_name
)
192 time_start
= time
.time()
194 graph
= template
.generate_graph(**kwargs
)
196 duration
= time
.time() - time_start
197 self
.log
.debug(_("Generated graph %s in %.1fms") \
198 % (template
, duration
* 1000))
203 class Object(object):
204 # The schema of the RRD database.
208 rra_types
= ("AVERAGE", "MIN", "MAX")
215 def __init__(self
, plugin
, *args
, **kwargs
):
218 # Indicates if this object has collected its data
219 self
.collected
= False
221 # Initialise this object
222 self
.init(*args
, **kwargs
)
224 # Create the database file.
228 return "<%s>" % self
.__class
__.__name
__
232 return self
.plugin
.collecty
236 return self
.plugin
.log
241 Returns a UNIQUE identifier for this object. As this is incorporated
242 into the path of RRD file, it must only contain ASCII characters.
244 raise NotImplementedError
249 The absolute path to the RRD file of this plugin.
251 filename
= self
._normalise
_filename
("%s.rrd" % self
.id)
253 return os
.path
.join(DATABASE_DIR
, self
.plugin
.path
, filename
)
256 def _normalise_filename(filename
):
257 # Convert the filename into ASCII characters only
258 filename
= unicodedata
.normalize("NFKC", filename
)
260 # Replace any spaces by dashes
261 filename
= filename
.replace(" ", "-")
267 def init(self
, *args
, **kwargs
):
269 Do some custom initialization stuff here.
275 Creates an empty RRD file with the desired data structures.
277 # Skip if the file does already exist.
278 if os
.path
.exists(self
.file):
281 dirname
= os
.path
.dirname(self
.file)
282 if not os
.path
.exists(dirname
):
285 # Create argument list.
286 args
= self
.get_rrd_schema()
288 rrdtool
.create(self
.file, *args
)
290 self
.log
.debug(_("Created RRD file %s.") % self
.file)
292 self
.log
.debug(" %s" % arg
)
295 return rrdtool
.info(self
.file)
299 return self
.plugin
.interval
303 return self
.stepsize
* 2
305 def get_rrd_schema(self
):
307 "--step", "%s" % self
.stepsize
,
309 for line
in self
.rrd_schema
:
310 if line
.startswith("DS:"):
312 (prefix
, name
, type, lower_limit
, upper_limit
) = line
.split(":")
318 "%s" % self
.heartbeat
,
329 for steps
, rows
in self
.rra_timespans
:
330 for type in self
.rra_types
:
331 schema
.append("RRA:%s:%s:%s:%s" % (type, xff
, steps
, rows
))
337 raise RuntimeError("This object has already collected its data")
339 self
.collected
= True
340 self
.now
= datetime
.datetime
.utcnow()
343 result
= self
.collect()
347 Will commit the collected data to the database.
349 # Make sure that the RRD database has been created
353 class GraphTemplate(object):
354 # A unique name to identify this graph template.
357 # Headline of the graph image
360 # Vertical label of the graph
361 graph_vertical_label
= None
367 # Instructions how to create the graph.
370 # Extra arguments passed to rrdgraph.
381 # Default dimensions for this graph
382 height
= GRAPH_DEFAULT_HEIGHT
383 width
= GRAPH_DEFAULT_WIDTH
385 def __init__(self
, plugin
, object_id
):
388 # Get all required RRD objects
389 self
.object_id
= object_id
391 # Get the main object
392 self
.object = self
.get_object(self
.object_id
)
395 return "<%s>" % self
.__class
__.__name
__
399 return self
.plugin
.collecty
403 return self
.plugin
.log
405 def _make_command_line(self
, interval
, format
=DEFAULT_IMAGE_FORMAT
,
406 width
=None, height
=None):
409 args
+= GRAPH_DEFAULT_ARGUMENTS
412 "--imgformat", format
,
413 "--height", "%s" % (height
or self
.height
),
414 "--width", "%s" % (width
or self
.width
),
417 args
+= self
.rrd_graph_args
421 args
+= ["--title", self
.graph_title
]
424 if self
.graph_vertical_label
:
425 args
+= ["--vertical-label", self
.graph_vertical_label
]
427 if self
.lower_limit
is not None or self
.upper_limit
is not None:
428 # Force to honour the set limits
429 args
.append("--rigid")
431 if self
.lower_limit
is not None:
432 args
+= ["--lower-limit", self
.lower_limit
]
434 if self
.upper_limit
is not None:
435 args
+= ["--upper-limit", self
.upper_limit
]
438 args
.append("--start")
441 args
.append(self
.intervals
[interval
])
443 args
.append(str(interval
))
447 def get_object(self
, *args
, **kwargs
):
448 return self
.plugin
.get_object(*args
, **kwargs
)
450 def get_object_table(self
):
452 "file" : self
.object,
456 def object_table(self
):
457 if not hasattr(self
, "_object_table"):
458 self
._object
_table
= self
.get_object_table()
460 return self
._object
_table
462 def get_object_files(self
):
465 for id, obj
in self
.object_table
.items():
470 def generate_graph(self
, interval
=None, **kwargs
):
471 args
= self
._make
_command
_line
(interval
, **kwargs
)
473 self
.log
.info(_("Generating graph %s") % self
)
474 self
.log
.debug(" args: %s" % args
)
476 object_files
= self
.get_object_files()
478 for item
in self
.rrd_graph
:
480 args
.append(item
% object_files
)
484 self
.log
.debug(" %s" % args
[-1])
486 # Convert arguments to string
487 args
= [str(e
) for e
in args
]
489 graph
= rrdtool
.graphv("-", *args
)
491 return graph
.get("image")