]>
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 ###############################################################################
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 if isinstance(result
, tuple) or isinstance(result
, list):
160 result
= ":".join(("%s" % e
for e
in result
))
162 self
.log
.warning(_("Unhandled exception in %s.collect()") % o
, exc_info
=True)
166 self
.log
.warning(_("Received empty result: %s") % o
)
169 self
.log
.debug(_("Collected %s: %s") % (o
, result
))
171 # Add the object to the write queue so that the data is written
172 # to the databases later.
173 self
.collecty
.write_queue
.add(o
, now
, result
)
175 # Returns the time this function took to complete.
176 return (time
.time() - time_start
)
179 self
.log
.debug(_("%s plugin has started") % self
.name
)
181 # Initially collect everything
188 # Wait until the timer has successfully elapsed.
189 if self
.timer
.wait():
190 delay
= self
.collect()
191 self
.timer
.reset(delay
)
193 self
.log
.debug(_("%s plugin has stopped") % self
.name
)
196 self
.log
.debug(_("Received shutdown signal."))
199 # Kill any running timers.
203 def get_object(self
, id):
204 for object in self
.objects
:
205 if not object.id == id:
210 def get_template(self
, template_name
, object_id
):
211 for template
in self
.templates
:
212 if not template
.name
== template_name
:
215 return template(self
, object_id
)
217 def generate_graph(self
, template_name
, object_id
="default", **kwargs
):
218 template
= self
.get_template(template_name
, object_id
=object_id
)
220 raise RuntimeError("Could not find template %s" % template_name
)
222 time_start
= time
.time()
224 graph
= template
.generate_graph(**kwargs
)
226 duration
= time
.time() - time_start
227 self
.log
.debug(_("Generated graph %s in %.1fms") \
228 % (template
, duration
* 1000))
233 class Object(object):
234 # The schema of the RRD database.
238 rra_types
= ["AVERAGE", "MIN", "MAX"]
239 rra_timespans
= [3600, 86400, 604800, 2678400, 31622400]
242 def __init__(self
, plugin
, *args
, **kwargs
):
245 # Indicates if this object has collected its data
246 self
.collected
= False
248 # Initialise this object
249 self
.init(*args
, **kwargs
)
251 # Create the database file.
255 return "<%s>" % self
.__class
__.__name
__
259 return self
.plugin
.collecty
263 return self
.plugin
.log
268 Returns a UNIQUE identifier for this object. As this is incorporated
269 into the path of RRD file, it must only contain ASCII characters.
271 raise NotImplementedError
276 The absolute path to the RRD file of this plugin.
278 return os
.path
.join(DATABASE_DIR
, self
.plugin
.path
, "%s.rrd" % self
.id)
282 def init(self
, *args
, **kwargs
):
284 Do some custom initialization stuff here.
290 Creates an empty RRD file with the desired data structures.
292 # Skip if the file does already exist.
293 if os
.path
.exists(self
.file):
296 dirname
= os
.path
.dirname(self
.file)
297 if not os
.path
.exists(dirname
):
300 # Create argument list.
301 args
= self
.get_rrd_schema()
303 rrdtool
.create(self
.file, *args
)
305 self
.log
.debug(_("Created RRD file %s.") % self
.file)
307 self
.log
.debug(" %s" % arg
)
310 return rrdtool
.info(self
.file)
314 return self
.plugin
.interval
318 return self
.stepsize
* 2
320 def get_rrd_schema(self
):
322 "--step", "%s" % self
.stepsize
,
324 for line
in self
.rrd_schema
:
325 if line
.startswith("DS:"):
327 (prefix
, name
, type, lower_limit
, upper_limit
) = line
.split(":")
333 "%s" % self
.heartbeat
,
345 for rra_timespan
in self
.rra_timespans
:
346 if (rra_timespan
/ self
.stepsize
) < self
.rra_rows
:
347 rra_timespan
= self
.stepsize
* self
.rra_rows
352 cdp_length
= rra_timespan
// (self
.rra_rows
* self
.stepsize
)
354 cdp_number
= math
.ceil(rra_timespan
/ (cdp_length
* self
.stepsize
))
356 for rra_type
in self
.rra_types
:
357 schema
.append("RRA:%s:%.10f:%d:%d" % \
358 (rra_type
, xff
, cdp_length
, cdp_number
))
364 raise RuntimeError("This object has already collected its data")
366 self
.collected
= True
367 self
.now
= datetime
.datetime
.utcnow()
370 result
= self
.collect()
374 Will commit the collected data to the database.
376 # Make sure that the RRD database has been created
380 class GraphTemplate(object):
381 # A unique name to identify this graph template.
384 # Headline of the graph image
387 # Vertical label of the graph
388 graph_vertical_label
= None
394 # Instructions how to create the graph.
397 # Extra arguments passed to rrdgraph.
408 # Default dimensions for this graph
409 height
= GRAPH_DEFAULT_HEIGHT
410 width
= GRAPH_DEFAULT_WIDTH
412 def __init__(self
, plugin
, object_id
):
415 # Get all required RRD objects
416 self
.object_id
= object_id
418 # Get the main object
419 self
.object = self
.get_object(self
.object_id
)
422 return "<%s>" % self
.__class
__.__name
__
426 return self
.plugin
.collecty
430 return self
.plugin
.log
432 def _make_command_line(self
, interval
, format
=DEFAULT_IMAGE_FORMAT
,
433 width
=None, height
=None):
436 args
+= GRAPH_DEFAULT_ARGUMENTS
439 "--imgformat", format
,
440 "--height", "%s" % (height
or self
.height
),
441 "--width", "%s" % (width
or self
.width
),
444 args
+= self
.rrd_graph_args
448 args
+= ["--title", self
.graph_title
]
451 if self
.graph_vertical_label
:
452 args
+= ["--vertical-label", self
.graph_vertical_label
]
454 if self
.lower_limit
is not None or self
.upper_limit
is not None:
455 # Force to honour the set limits
456 args
.append("--rigid")
458 if self
.lower_limit
is not None:
459 args
+= ["--lower-limit", self
.lower_limit
]
461 if self
.upper_limit
is not None:
462 args
+= ["--upper-limit", self
.upper_limit
]
465 args
.append("--start")
468 args
.append(self
.intervals
[interval
])
470 args
.append(str(interval
))
474 def get_object(self
, *args
, **kwargs
):
475 return self
.plugin
.get_object(*args
, **kwargs
)
477 def get_object_table(self
):
479 "file" : self
.object,
482 def get_object_files(self
):
485 for id, obj
in self
.get_object_table().items():
490 def generate_graph(self
, interval
=None, **kwargs
):
491 args
= self
._make
_command
_line
(interval
, **kwargs
)
493 self
.log
.info(_("Generating graph %s") % self
)
494 self
.log
.debug(" args: %s" % args
)
496 object_files
= self
.get_object_files()
498 for item
in self
.rrd_graph
:
500 args
.append(item
% object_files
)
504 self
.log
.debug(" %s" % args
[-1])
506 return self
.write_graph(*args
)
508 def write_graph(self
, *args
):
509 # Convert all arguments to string
510 args
= [str(e
) for e
in args
]
512 with tempfile
.NamedTemporaryFile() as f
:
513 rrdtool
.graph(f
.name
, *args
)
515 # Get back to the beginning of the file
518 # Return all the content