]>
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
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 self
.collecty
= collecty
109 # Check if this plugin was configured correctly.
110 assert self
.name
, "Name of the plugin is not set: %s" % self
.name
111 assert self
.description
, "Description of the plugin is not set: %s" % self
.description
113 # Initialize the logger.
114 self
.log
= logging
.getLogger("collecty.plugins.%s" % self
.name
)
115 self
.log
.propagate
= 1
119 # Run some custom initialization.
122 self
.log
.debug(_("Successfully initialized %s") % self
.__class
__.__name
__)
127 Returns the name of the sub directory in which all RRD files
128 for this plugin should be stored in.
134 def init(self
, **kwargs
):
136 Do some custom initialization stuff here.
142 Gathers the statistical data, this plugin collects.
144 time_start
= time
.time()
146 # Run through all objects of this plugin and call the collect method.
147 for o
in self
.objects
:
148 now
= datetime
.datetime
.utcnow()
152 if isinstance(result
, tuple) or isinstance(result
, list):
153 result
= ":".join(("%s" % e
for e
in result
))
155 self
.log
.warning(_("Unhandled exception in %s.collect()") % o
, exc_info
=True)
159 self
.log
.warning(_("Received empty result: %s") % o
)
162 self
.log
.debug(_("Collected %s: %s") % (o
, result
))
164 # Add the object to the write queue so that the data is written
165 # to the databases later.
166 self
.collecty
.write_queue
.add(o
, now
, result
)
168 # Returns the time this function took to complete.
169 delay
= time
.time() - time_start
171 # Log some warning when a collect method takes too long to return some data
173 self
.log
.warning(_("A worker thread was stalled for %.4fs") % delay
)
175 def get_object(self
, id):
176 for object in self
.objects
:
177 if not object.id == id:
182 def get_template(self
, template_name
, object_id
):
183 for template
in self
.templates
:
184 if not template
.name
== template_name
:
187 return template(self
, object_id
)
189 def generate_graph(self
, template_name
, object_id
="default", **kwargs
):
190 template
= self
.get_template(template_name
, object_id
=object_id
)
192 raise RuntimeError("Could not find template %s" % template_name
)
194 time_start
= time
.time()
196 graph
= template
.generate_graph(**kwargs
)
198 duration
= time
.time() - time_start
199 self
.log
.debug(_("Generated graph %s in %.1fms") \
200 % (template
, duration
* 1000))
205 class Object(object):
206 # The schema of the RRD database.
210 rra_types
= ["AVERAGE", "MIN", "MAX"]
211 rra_timespans
= [3600, 86400, 604800, 2678400, 31622400]
214 def __init__(self
, plugin
, *args
, **kwargs
):
217 # Indicates if this object has collected its data
218 self
.collected
= False
220 # Initialise this object
221 self
.init(*args
, **kwargs
)
223 # Create the database file.
227 return "<%s>" % self
.__class
__.__name
__
231 return self
.plugin
.collecty
235 return self
.plugin
.log
240 Returns a UNIQUE identifier for this object. As this is incorporated
241 into the path of RRD file, it must only contain ASCII characters.
243 raise NotImplementedError
248 The absolute path to the RRD file of this plugin.
250 filename
= self
._normalise
_filename
("%s.rrd" % self
.id)
252 return os
.path
.join(DATABASE_DIR
, self
.plugin
.path
, filename
)
255 def _normalise_filename(filename
):
256 # Convert the filename into ASCII characters only
257 filename
= filename
.encode("ascii", "ignore")
259 # Replace any spaces by dashes
260 filename
= filename
.replace(" ", "-")
266 def init(self
, *args
, **kwargs
):
268 Do some custom initialization stuff here.
274 Creates an empty RRD file with the desired data structures.
276 # Skip if the file does already exist.
277 if os
.path
.exists(self
.file):
280 dirname
= os
.path
.dirname(self
.file)
281 if not os
.path
.exists(dirname
):
284 # Create argument list.
285 args
= self
.get_rrd_schema()
287 rrdtool
.create(self
.file, *args
)
289 self
.log
.debug(_("Created RRD file %s.") % self
.file)
291 self
.log
.debug(" %s" % arg
)
294 return rrdtool
.info(self
.file)
298 return self
.plugin
.interval
302 return self
.stepsize
* 2
304 def get_rrd_schema(self
):
306 "--step", "%s" % self
.stepsize
,
308 for line
in self
.rrd_schema
:
309 if line
.startswith("DS:"):
311 (prefix
, name
, type, lower_limit
, upper_limit
) = line
.split(":")
317 "%s" % self
.heartbeat
,
329 for rra_timespan
in self
.rra_timespans
:
330 if (rra_timespan
/ self
.stepsize
) < self
.rra_rows
:
331 rra_timespan
= self
.stepsize
* self
.rra_rows
336 cdp_length
= rra_timespan
// (self
.rra_rows
* self
.stepsize
)
338 cdp_number
= math
.ceil(rra_timespan
/ (cdp_length
* self
.stepsize
))
340 for rra_type
in self
.rra_types
:
341 schema
.append("RRA:%s:%.10f:%d:%d" % \
342 (rra_type
, xff
, cdp_length
, cdp_number
))
348 raise RuntimeError("This object has already collected its data")
350 self
.collected
= True
351 self
.now
= datetime
.datetime
.utcnow()
354 result
= self
.collect()
358 Will commit the collected data to the database.
360 # Make sure that the RRD database has been created
364 class GraphTemplate(object):
365 # A unique name to identify this graph template.
368 # Headline of the graph image
371 # Vertical label of the graph
372 graph_vertical_label
= None
378 # Instructions how to create the graph.
381 # Extra arguments passed to rrdgraph.
392 # Default dimensions for this graph
393 height
= GRAPH_DEFAULT_HEIGHT
394 width
= GRAPH_DEFAULT_WIDTH
396 def __init__(self
, plugin
, object_id
):
399 # Get all required RRD objects
400 self
.object_id
= object_id
402 # Get the main object
403 self
.object = self
.get_object(self
.object_id
)
406 return "<%s>" % self
.__class
__.__name
__
410 return self
.plugin
.collecty
414 return self
.plugin
.log
416 def _make_command_line(self
, interval
, format
=DEFAULT_IMAGE_FORMAT
,
417 width
=None, height
=None):
420 args
+= GRAPH_DEFAULT_ARGUMENTS
423 "--imgformat", format
,
424 "--height", "%s" % (height
or self
.height
),
425 "--width", "%s" % (width
or self
.width
),
428 args
+= self
.rrd_graph_args
432 args
+= ["--title", self
.graph_title
]
435 if self
.graph_vertical_label
:
436 args
+= ["--vertical-label", self
.graph_vertical_label
]
438 if self
.lower_limit
is not None or self
.upper_limit
is not None:
439 # Force to honour the set limits
440 args
.append("--rigid")
442 if self
.lower_limit
is not None:
443 args
+= ["--lower-limit", self
.lower_limit
]
445 if self
.upper_limit
is not None:
446 args
+= ["--upper-limit", self
.upper_limit
]
449 args
.append("--start")
452 args
.append(self
.intervals
[interval
])
454 args
.append(str(interval
))
458 def get_object(self
, *args
, **kwargs
):
459 return self
.plugin
.get_object(*args
, **kwargs
)
461 def get_object_table(self
):
463 "file" : self
.object,
467 def object_table(self
):
468 if not hasattr(self
, "_object_table"):
469 self
._object
_table
= self
.get_object_table()
471 return self
._object
_table
473 def get_object_files(self
):
476 for id, obj
in self
.object_table
.items():
481 def generate_graph(self
, interval
=None, **kwargs
):
482 args
= self
._make
_command
_line
(interval
, **kwargs
)
484 self
.log
.info(_("Generating graph %s") % self
)
485 self
.log
.debug(" args: %s" % args
)
487 object_files
= self
.get_object_files()
489 for item
in self
.rrd_graph
:
491 args
.append(item
% object_files
)
495 self
.log
.debug(" %s" % args
[-1])
497 return self
.write_graph(*args
)
499 def write_graph(self
, *args
):
500 # Convert all arguments to string
501 args
= [str(e
) for e
in args
]
503 with tempfile
.NamedTemporaryFile() as f
:
504 rrdtool
.graph(f
.name
, *args
)
506 # Get back to the beginning of the file
509 # Return all the content