]>
git.ipfire.org Git - collecty.git/blob - src/collecty/plugins/base.py
1a03105eeabbcd97af4ba744a7d19ea13ee356c3
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 ###############################################################################
22 from __future__
import division
33 from ..constants
import *
40 Returns a list with all automatically registered plugins.
42 return _plugins
.values()
45 def __init__(self
, timeout
, heartbeat
=1):
46 self
.timeout
= timeout
47 self
.heartbeat
= heartbeat
53 def reset(self
, delay
=0):
55 self
.start
= time
.time()
59 # Has this timer been killed?
64 return time
.time() - self
.start
- self
.delay
70 while self
.elapsed
< self
.timeout
and not self
.killed
:
71 time
.sleep(self
.heartbeat
)
73 return self
.elapsed
> self
.timeout
76 class Plugin(threading
.Thread
):
77 # The name of this plugin.
80 # A description for this plugin.
83 # Templates which can be used to generate a graph out of
84 # the data from this data source.
87 # The default interval for all plugins
90 # Automatically register all providers.
91 class __metaclass__(type):
92 def __init__(plugin
, name
, bases
, dict):
93 type.__init
__(plugin
, name
, bases
, dict)
95 # The main class from which is inherited is not registered
100 if not all((plugin
.name
, plugin
.description
)):
101 raise RuntimeError(_("Plugin is not properly configured: %s") \
104 _plugins
[plugin
.name
] = plugin
106 def __init__(self
, collecty
, **kwargs
):
107 threading
.Thread
.__init
__(self
, name
=self
.description
)
110 self
.collecty
= collecty
112 # Check if this plugin was configured correctly.
113 assert self
.name
, "Name of the plugin is not set: %s" % self
.name
114 assert self
.description
, "Description of the plugin is not set: %s" % self
.description
116 # Initialize the logger.
117 self
.log
= logging
.getLogger("collecty.plugins.%s" % self
.name
)
118 self
.log
.propagate
= 1
122 # Run some custom initialization.
127 self
.timer
= Timer(self
.interval
)
129 self
.log
.debug(_("Successfully initialized %s") % self
.__class
__.__name
__)
134 Returns the name of the sub directory in which all RRD files
135 for this plugin should be stored in.
141 def init(self
, **kwargs
):
143 Do some custom initialization stuff here.
149 Gathers the statistical data, this plugin collects.
151 time_start
= time
.time()
153 # Run through all objects of this plugin and call the collect method.
154 for o
in self
.objects
:
155 now
= datetime
.datetime
.utcnow()
159 self
.log
.warning(_("Unhandled exception in %s.collect()") % o
, exc_info
=True)
163 self
.log
.warning(_("Received empty result: %s") % o
)
166 self
.log
.debug(_("Collected %s: %s") % (o
, result
))
168 # Add the object to the write queue so that the data is written
169 # to the databases later.
170 self
.collecty
.write_queue
.add(o
, now
, result
)
172 # Returns the time this function took to complete.
173 return (time
.time() - time_start
)
176 self
.log
.debug(_("%s plugin has started") % self
.name
)
178 # Initially collect everything
185 # Wait until the timer has successfully elapsed.
186 if self
.timer
.wait():
187 delay
= self
.collect()
188 self
.timer
.reset(delay
)
190 self
.log
.debug(_("%s plugin has stopped") % self
.name
)
193 self
.log
.debug(_("Received shutdown signal."))
196 # Kill any running timers.
200 def get_object(self
, id):
201 for object in self
.objects
:
202 if not object.id == id:
207 def get_template(self
, template_name
):
208 for template
in self
.templates
:
209 if not template
.name
== template_name
:
212 return template(self
)
214 def generate_graph(self
, template_name
, object_id
="default", **kwargs
):
215 template
= self
.get_template(template_name
)
217 raise RuntimeError("Could not find template %s" % template_name
)
219 time_start
= time
.time()
221 graph
= template
.generate_graph(object_id
=object_id
, **kwargs
)
223 duration
= time
.time() - time_start
224 self
.log
.debug(_("Generated graph %s in %.1fms") \
225 % (template
, duration
* 1000))
230 class Object(object):
231 # The schema of the RRD database.
235 rra_types
= ["AVERAGE", "MIN", "MAX"]
236 rra_timespans
= [3600, 86400, 604800, 2678400, 31622400]
239 def __init__(self
, plugin
, *args
, **kwargs
):
242 # Indicates if this object has collected its data
243 self
.collected
= False
245 # Initialise this object
246 self
.init(*args
, **kwargs
)
248 # Create the database file.
252 return "<%s>" % self
.__class
__.__name
__
256 return self
.plugin
.collecty
260 return self
.plugin
.log
265 Returns a UNIQUE identifier for this object. As this is incorporated
266 into the path of RRD file, it must only contain ASCII characters.
268 raise NotImplementedError
273 The absolute path to the RRD file of this plugin.
275 return os
.path
.join(DATABASE_DIR
, self
.plugin
.path
, "%s.rrd" % self
.id)
279 def init(self
, *args
, **kwargs
):
281 Do some custom initialization stuff here.
287 Creates an empty RRD file with the desired data structures.
289 # Skip if the file does already exist.
290 if os
.path
.exists(self
.file):
293 dirname
= os
.path
.dirname(self
.file)
294 if not os
.path
.exists(dirname
):
297 # Create argument list.
298 args
= self
.get_rrd_schema()
300 rrdtool
.create(self
.file, *args
)
302 self
.log
.debug(_("Created RRD file %s.") % self
.file)
304 self
.log
.debug(" %s" % arg
)
307 return rrdtool
.info(self
.file)
311 return self
.plugin
.interval
315 return self
.stepsize
* 2
317 def get_rrd_schema(self
):
319 "--step", "%s" % self
.stepsize
,
321 for line
in self
.rrd_schema
:
322 if line
.startswith("DS:"):
324 (prefix
, name
, type, lower_limit
, upper_limit
) = line
.split(":")
330 "%s" % self
.heartbeat
,
342 for rra_timespan
in self
.rra_timespans
:
343 if (rra_timespan
/ self
.stepsize
) < self
.rra_rows
:
344 rra_timespan
= self
.stepsize
* self
.rra_rows
349 cdp_length
= rra_timespan
// (self
.rra_rows
* self
.stepsize
)
351 cdp_number
= math
.ceil(rra_timespan
/ (cdp_length
* self
.stepsize
))
353 for rra_type
in self
.rra_types
:
354 schema
.append("RRA:%s:%.10f:%d:%d" % \
355 (rra_type
, xff
, cdp_length
, cdp_number
))
361 raise RuntimeError("This object has already collected its data")
363 self
.collected
= True
364 self
.now
= datetime
.datetime
.utcnow()
367 result
= self
.collect()
371 Will commit the collected data to the database.
373 # Make sure that the RRD database has been created
377 class GraphTemplate(object):
378 # A unique name to identify this graph template.
381 # Instructions how to create the graph.
384 # Extra arguments passed to rrdgraph.
395 # Default dimensions for this graph
396 height
= GRAPH_DEFAULT_HEIGHT
397 width
= GRAPH_DEFAULT_WIDTH
399 def __init__(self
, plugin
):
403 return "<%s>" % self
.__class
__.__name
__
407 return self
.plugin
.collecty
411 return self
.plugin
.log
413 def _make_command_line(self
, interval
, format
=DEFAULT_IMAGE_FORMAT
,
414 width
=None, height
=None):
417 args
+= GRAPH_DEFAULT_ARGUMENTS
420 "--imgformat", format
,
421 "--height", "%s" % (height
or self
.height
),
422 "--width", "%s" % (width
or self
.width
),
425 args
+= self
.rrd_graph_args
428 args
.append("--start")
431 args
.append(self
.intervals
[interval
])
433 args
.append(str(interval
))
437 def get_object_table(self
, object_id
):
439 "file" : self
.plugin
.get_object(object_id
),
442 def get_object_files(self
, object_id
):
445 for id, obj
in self
.get_object_table(object_id
).items():
450 def generate_graph(self
, object_id
, interval
=None, **kwargs
):
451 args
= self
._make
_command
_line
(interval
, **kwargs
)
453 self
.log
.info(_("Generating graph %s") % self
)
454 self
.log
.debug(" args: %s" % args
)
456 object_files
= self
.get_object_files(object_id
)
458 for item
in self
.rrd_graph
:
460 args
.append(item
% object_files
)
464 return self
.write_graph(*args
)
466 def write_graph(self
, *args
):
467 # Convert all arguments to string
468 args
= [str(e
) for e
in args
]
470 with tempfile
.NamedTemporaryFile() as f
:
471 rrdtool
.graph(f
.name
, *args
)
473 # Get back to the beginning of the file
476 # Return all the content