This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "Statistics collection daemon.".
The branch, master has been updated via 4dc6b0c93eae269afa6e2597d3620ce152f06c70 (commit) via 708c842f2ef963fd343ba5cf75eb512438d643ad (commit) via 0de83e4d7d6678029ff94681f9472a431ef72df3 (commit) via a116f2fb7c8d59894bcdce7a9f3306a2e3ac3470 (commit) via 13b7bc4615e0c11f2323ca6feffdf9b789a80250 (commit) via bf012438797b0c0fda425892b754f6c8d1867634 (commit) via 6b8cd5784722d87772075bb7558003e4ad3ff050 (commit) via 49ce926e3c7f810c288aaf4be7ec7402eec285df (commit) via cc19aed8652e5776b6da451d4de1e55ce62ced58 (commit) via 4ac0cdf084645e2aac15c2129bcbb1d414b4212c (commit) via 5a2ff9596d1f19116d994c461aecdd3f381eb9a9 (commit) via b01622486e48f563b63e4a129179ac7b79caf519 (commit) via eed405de851a1cf80748dcd31ca1995f97e126ab (commit) via 05a55b561989408a1f2198ef84f077f36d36672f (commit) via cd57e2f39e42ce2e4f348d3034ba344beef2a5ad (commit) from 54e10690b6ddb3177d069a976002256cac719145 (commit)
Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below.
- Log ----------------------------------------------------------------- commit 4dc6b0c93eae269afa6e2597d3620ce152f06c70 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 16:20:50 2012 +0000
Let the main thread regularly submit all data to disk.
commit 708c842f2ef963fd343ba5cf75eb512438d643ad Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 15:51:55 2012 +0000
memory: Update plugin.
commit 0de83e4d7d6678029ff94681f9472a431ef72df3 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 15:44:22 2012 +0000
memory: Update plugin.
commit a116f2fb7c8d59894bcdce7a9f3306a2e3ac3470 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 15:43:48 2012 +0000
cpu: Automatically create one instance.
commit 13b7bc4615e0c11f2323ca6feffdf9b789a80250 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 15:42:12 2012 +0000
Enable plugins to automatically create instances of them.
So, we have some kind of autoconfiguration for the most things.
commit bf012438797b0c0fda425892b754f6c8d1867634 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 14:41:48 2012 +0000
Add constants file.
commit 6b8cd5784722d87772075bb7558003e4ad3ff050 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 14:40:57 2012 +0000
Format log messages, so one can see the origin of the message.
commit 49ce926e3c7f810c288aaf4be7ec7402eec285df Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 14:23:48 2012 +0000
Get rid of python-daemon. Properly handle signals.
commit cc19aed8652e5776b6da451d4de1e55ce62ced58 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 12:36:28 2012 +0000
Update the CPU plugin for new schema.
commit 4ac0cdf084645e2aac15c2129bcbb1d414b4212c Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 12:36:08 2012 +0000
plugins: Restructure the plugin concept.
commit 5a2ff9596d1f19116d994c461aecdd3f381eb9a9 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 12:35:38 2012 +0000
Add some basic logging.
commit b01622486e48f563b63e4a129179ac7b79caf519 Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 10:27:44 2012 +0000
Fix installation.
commit eed405de851a1cf80748dcd31ca1995f97e126ab Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 10:21:52 2012 +0000
plugins: Split all plugins into separate files.
commit 05a55b561989408a1f2198ef84f077f36d36672f Author: Michael Tremer michael.tremer@ipfire.org Date: Sat Aug 4 09:53:57 2012 +0000
Create an i18n submodule.
commit cd57e2f39e42ce2e4f348d3034ba344beef2a5ad Author: Michael Tremer michael.tremer@ipfire.org Date: Fri Aug 3 14:03:12 2012 +0000
Add proper license headers.
-----------------------------------------------------------------------
Summary of changes: Makefile | 25 +++- collecty/__init__.py | 114 ++++++++++++- collecty/constants.py | 22 +++ collecty/i18n.py | 38 ++++ collecty/plugins/__init__.py | 384 ++++-------------------------------------- collecty/plugins/base.py | 218 ++++++++++++++++++++++++ collecty/plugins/cpu.py | 140 +++++++++++++++ collecty/plugins/loadavg.py | 78 +++++++++ collecty/plugins/memory.py | 124 ++++++++++++++ collectyd | 48 +++--- 10 files changed, 807 insertions(+), 384 deletions(-) create mode 100644 collecty/constants.py create mode 100644 collecty/i18n.py create mode 100644 collecty/plugins/base.py create mode 100644 collecty/plugins/cpu.py create mode 100644 collecty/plugins/loadavg.py create mode 100644 collecty/plugins/memory.py
Difference in files: diff --git a/Makefile b/Makefile index 42a17e3..2e1b018 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,29 @@ +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +###############################################################################
NAME = collecty VERSION = 0.0.1
DESTDIR = -PYTHON_VER := $(shell python --version 2>&1 | awk '{ print $$NF }') -PYTHON_DIR = $(DESTDIR)/usr/lib/python$(PYTHON_VER)/site-packages/$(NAME)/ +PYTHON_VER := $(shell python -c "import platform; print '.'.join(platform.python_version_tuple()[:2])") +PYTHON_DIR = $(DESTDIR)/usr/lib/python$(PYTHON_VER)/site-packages/
all:
@@ -15,7 +34,7 @@ dist: install: -mkdir -pv $(PYTHON_DIR) cp -rvf collecty $(PYTHON_DIR) - install -v -m 755 colletyd $(DESTDIR)/usr/sbin + install -v -m 755 collectyd $(DESTDIR)/usr/sbin
-mkdir -pv $(DESTDIR)/var/rrd
diff --git a/collecty/__init__.py b/collecty/__init__.py index c799b53..5e684f4 100644 --- a/collecty/__init__.py +++ b/collecty/__init__.py @@ -1,19 +1,83 @@ #!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +###############################################################################
import signal +import time
import ConfigParser as configparser
import plugins
+from i18n import _ + +# Initialize logging. +import logging +log = logging.getLogger("collecty") +log.level = logging.DEBUG + +handler = logging.StreamHandler() +handler.setLevel(logging.DEBUG) +log.handlers.append(handler) + +formatter = logging.Formatter("%(asctime)s | %(name)-20s - %(levelname)-6s | %(message)s") +handler.setFormatter(formatter) + class ConfigError(Exception): pass
class Collecty(object): + # The default interval, when all data is written to disk. + SUBMIT_INTERVAL = 300 + + HEARTBEAT = 2 + def __init__(self): self.config = configparser.ConfigParser() self.instances = []
+ # Indicates whether this process should be running or not. + self.running = True + + # Add all automatic plugins. + self.add_autocreate_plugins() + + log.info(_("Collecty successfully initialized.")) + + def add_autocreate_plugins(self): + for plugin in plugins.registered_plugins: + if not hasattr(plugin, "autocreate"): + continue + + ret = plugin.autocreate(self) + if not ret: + continue + + if not type(ret) == type([]): + ret = [ret,] + + log.debug(_("Plugin '%(name)s' registered %(number)s instance(s).") % \ + { "name" : plugin.name, "number" : len(ret) }) + + self.instances += ret + def read_config(self, config): self.config.read(config) @@ -37,16 +101,56 @@ class Collecty(object): i = plugin(self, **kwargs) self.instances.append(i)
- def debug(self, message): - print message - def run(self): - signal.signal(signal.SIGTERM, lambda *args: self.shutdown()) + # Register signal handlers. + self.register_signal_handler()
+ # Start all plugin instances. for i in self.instances: i.start()
+ # Regularly submit all data to disk. + counter = self.SUBMIT_INTERVAL / self.HEARTBEAT + while self.running: + time.sleep(self.HEARTBEAT) + counter -= 1 + + if counter == 0: + self.submit_all() + counter = self.SUBMIT_INTERVAL / self.HEARTBEAT + + # Wait until all instances are finished. + while self.instances: + for instance in self.instances[:]: + if not instance.isAlive(): + log.debug(_("%s is not alive anymore. Removing.") % instance) + self.instances.remove(instance) + + # Wait a bit. + time.sleep(0.1) + + log.debug(_("No thread running. Exiting main thread.")) + + def submit_all(self): + """ + Submit all data right now. + """ + for i in self.instances: + i._submit() + def shutdown(self): + log.debug(_("Received shutdown signal")) + + self.running = False + + # Propagating shutdown to all threads. for i in self.instances: - self.debug("Stopping %s..." % i) i.shutdown() + + def register_signal_handler(self): + for s in (signal.SIGTERM, signal.SIGINT): + signal.signal(s, self.signal_handler) + + def signal_handler(self, *args, **kwargs): + # Shutdown this application. + self.shutdown() diff --git a/collecty/constants.py b/collecty/constants.py new file mode 100644 index 0000000..969a083 --- /dev/null +++ b/collecty/constants.py @@ -0,0 +1,22 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +DATABASE_DIR = "/var/lib/collecty" diff --git a/collecty/i18n.py b/collecty/i18n.py new file mode 100644 index 0000000..75c78fe --- /dev/null +++ b/collecty/i18n.py @@ -0,0 +1,38 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import gettext + +TEXTDOMAIN = "collecty" + +N_ = lambda x: x + +def _(singular, plural=None, n=None): + """ + A function that returnes the translation of a string if available. + + The language is taken from the system environment. + """ + if not plural is None: + assert n is not None + return gettext.dngettext(TEXTDOMAIN, singular, plural, n) + + return gettext.dgettext(TEXTDOMAIN, singular) diff --git a/collecty/plugins/__init__.py b/collecty/plugins/__init__.py index 74774b7..a8af815 100644 --- a/collecty/plugins/__init__.py +++ b/collecty/plugins/__init__.py @@ -1,354 +1,30 @@ - -import os - -import rrdtool -import time - -from threading import Thread - -import gettext -_ = lambda x: gettext.ldgettext("collecty", x) - -registered_plugins = [] - -def find(name): - for plugin in registered_plugins: - if plugin._type == name: - return plugin - -def register(plugin): - registered_plugins.append(plugin) - -class Plugin(Thread): - def __init__(self, collecty, **kwargs): - Thread.__init__(self) - self.collecty = collecty - - self.interval = int(kwargs.get("interval", 60)) - - # Keepalive options - self.heartbeat = 2 - self.killed = False - - self.wakeup = self.interval / self.heartbeat - - self.file = kwargs.get("file", None) - if not self.file.startswith("/"): - self.file = os.path.join("/var/rrd", self.file) - - self.data = [] - - self.create() - - def __repr__(self): - return "<Plugin %s>" % self._type - - def __str__(self): - return "Plugin %s %s" % (self._type, self.file) - - def run(self): - self.collecty.debug("%s started..." % self) - - c = 0 - while True: - if self.killed: - self.update() - self.collecty.debug("%s stoppped..." % self) - return - - if c == 0: - self.data.append(self.collect()) - self.collecty.debug("%s collectd: %s..." % (self, self.data[-1])) - - self.update() - - c = self.wakeup - - c = c - 1 - time.sleep(self.heartbeat) - - def shutdown(self): - self.killed = True - - def time(self): - return int(time.time()) # Should return time as int in UTC - - def create(self): - if not os.path.exists(self.file): - rrdtool.create(self.file, *self._rrd) - - def update(self): - if self.data: - self.collecty.debug("%s saving data..." % self) - rrdtool.update(self.file, *self.data) - self.data = [] - - def collect(self): - raise Exception, "Not implemented" - - def graph(self, file, interval=None): - args = [ "--imgformat", "PNG", - "-w", "580", # Width of the graph - "-h", "240", # Height of the graph - "--interlaced", "--slope-mode", ] - - intervals = { None : "-3h", - "hour" : "-1h", - "day" : "-25h", - "week" : "-360h" } - - args.append("--start") - if intervals.has_key(interval): - args.append(intervals[interval]) - else: - args.append(interval) - - info = { "file" : self.file } - for item in self._graph: - try: - args.append(item % info) - except TypeError: - args.append(item) - - rrdtool.graph(file, *args) - - def info(self): - return rrdtool.info(self.file) - - -class PluginCpu(Plugin): - _name = "CPU Usage Plugin" - _type = "cpu" - - _rrd = [ "DS:user:GAUGE:120:0:100", - "DS:nice:GAUGE:120:0:100", - "DS:sys:GAUGE:120:0:100", - "DS:idle:GAUGE:120:0:100", - "DS:wait:GAUGE:120:0:100", - "DS:interrupt:GAUGE:120:0:100", - "RRA:AVERAGE:0.5:1:2160", - "RRA:AVERAGE:0.5:5:2016", - "RRA:AVERAGE:0.5:15:2880", - "RRA:AVERAGE:0.5:60:8760" ] - - _graph = [ "DEF:user=%(file)s:user:AVERAGE", - "DEF:nice=%(file)s:nice:AVERAGE", - "DEF:sys=%(file)s:sys:AVERAGE", - "DEF:idle=%(file)s:idle:AVERAGE", - "DEF:wait=%(file)s:wait:AVERAGE", - "DEF:interrupt=%(file)s:interrupt:AVERAGE", - "AREA:user#ff0000:%-15s" % _("User"), - "VDEF:usermin=user,MINIMUM", - "VDEF:usermax=user,MAXIMUM", - "VDEF:useravg=user,AVERAGE", - "GPRINT:usermax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:usermin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:useravg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:nice#ff3300:%-15s" % _("Nice"), - "VDEF:nicemin=nice,MINIMUM", - "VDEF:nicemax=nice,MAXIMUM", - "VDEF:niceavg=nice,AVERAGE", - "GPRINT:nicemax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:nicemin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:niceavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:sys#ff6600:%-15s" % _("System"), - "VDEF:sysmin=sys,MINIMUM", - "VDEF:sysmax=sys,MAXIMUM", - "VDEF:sysavg=sys,AVERAGE", - "GPRINT:sysmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:sysmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:sysavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:wait#ff9900:%-15s" % _("Wait"), - "VDEF:waitmin=wait,MINIMUM", - "VDEF:waitmax=wait,MAXIMUM", - "VDEF:waitavg=wait,AVERAGE", - "GPRINT:waitmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:waitmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:waitavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:interrupt#ffcc00:%-15s" % _("Interrupt"), - "VDEF:interruptmin=interrupt,MINIMUM", - "VDEF:interruptmax=interrupt,MAXIMUM", - "VDEF:interruptavg=interrupt,AVERAGE", - "GPRINT:interruptmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:interruptmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:interruptavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:idle#ffff00:%-15s" % _("Idle"), - "VDEF:idlemin=idle,MINIMUM", - "VDEF:idlemax=idle,MAXIMUM", - "VDEF:idleavg=idle,AVERAGE", - "GPRINT:idlemax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:idlemin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:idleavg:%12s:" % _("Average") + " %6.2lf\n", ] - - def __init__(self, collecty, **kwargs): - Plugin.__init__(self, collecty, **kwargs) - - def collect(self): - ret = "%s" % self.time() - f = open("/proc/stat") - for line in f.readlines(): - if not line.startswith("cpu"): - continue - a = line.split() - if len(a) < 6: - continue - - user = float(a[1]) - nice = float(a[2]) - sys = float(a[3]) - idle = float(a[4]) - wait = float(a[5]) - interrupt = float(a[6]) - sum = float(user + nice + sys + idle + wait + interrupt) - - ret += ":%s" % (user * 100 / sum) - ret += ":%s" % (nice * 100 / sum) - ret += ":%s" % (sys * 100 / sum) - ret += ":%s" % (idle * 100 / sum) - ret += ":%s" % (wait * 100 / sum) - ret += ":%s" % (interrupt * 100 / sum) - break - - f.close() - return ret - -register(PluginCpu) - - -class PluginLoad(Plugin): - _name = "Loadaverage Plugin" - _type = "load" - - _rrd = ["DS:load1:GAUGE:120:0:U", - "DS:load5:GAUGE:120:0:U", - "DS:load15:GAUGE:120:0:U", - "RRA:AVERAGE:0.5:1:2160", - "RRA:AVERAGE:0.5:5:2016", - "RRA:AVERAGE:0.5:15:2880", - "RRA:AVERAGE:0.5:60:8760" ] - - _graph = [ "DEF:load1=%(file)s:load1:AVERAGE", - "DEF:load5=%(file)s:load5:AVERAGE", - "DEF:load15=%(file)s:load15:AVERAGE", - "AREA:load1#ff0000:%s" % _("Load average 1m"), - "VDEF:load1min=load1,MINIMUM", - "VDEF:load1max=load1,MAXIMUM", - "VDEF:load1avg=load1,AVERAGE", - "GPRINT:load1max:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:load1min:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:load1avg:%12s:" % _("Average") + " %6.2lf\n", - "AREA:load5#ff9900:%s" % _("Load average 5m"), - "VDEF:load5min=load5,MINIMUM", - "VDEF:load5max=load5,MAXIMUM", - "VDEF:load5avg=load5,AVERAGE", - "GPRINT:load5max:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:load5min:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:load5avg:%12s:" % _("Average") + " %6.2lf\n", - "AREA:load15#ffff00:%s" % _("Load average 15m"), - "VDEF:load15min=load15,MINIMUM", - "VDEF:load15max=load15,MAXIMUM", - "VDEF:load15avg=load15,AVERAGE", - "GPRINT:load15max:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:load15min:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:load15avg:%12s:" % _("Average") + " %6.2lf\n", - "LINE:load5#dd8800", - "LINE:load1#dd0000", ] - - def __init__(self, collecty, **kwargs): - Plugin.__init__(self, collecty, **kwargs) - - def collect(self): - ret = "%s" % self.time() - for load in os.getloadavg(): - ret += ":%s" % load - return ret - -register(PluginLoad) - - -class PluginMem(Plugin): - _name = "Memory Usage Plugin" - _type = "mem" - - _rrd = ["DS:used:GAUGE:120:0:100", - "DS:cached:GAUGE:120:0:100", - "DS:buffered:GAUGE:120:0:100", - "DS:free:GAUGE:120:0:100", - "DS:swap:GAUGE:120:0:100", - "RRA:AVERAGE:0.5:1:2160", - "RRA:AVERAGE:0.5:5:2016", - "RRA:AVERAGE:0.5:15:2880", - "RRA:AVERAGE:0.5:60:8760" ] - - _graph = [ "DEF:used=%(file)s:used:AVERAGE", - "DEF:cached=%(file)s:cached:AVERAGE", - "DEF:buffered=%(file)s:buffered:AVERAGE", - "DEF:free=%(file)s:free:AVERAGE", - "DEF:swap=%(file)s:swap:AVERAGE", - "AREA:used#0000ee:%-15s" % _("Used memory"), - "VDEF:usedmin=used,MINIMUM", - "VDEF:usedmax=used,MAXIMUM", - "VDEF:usedavg=used,AVERAGE", - "GPRINT:usedmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:usedmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:usedavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:cached#0099ee:%-15s" % _("Cached data"), - "VDEF:cachedmin=cached,MINIMUM", - "VDEF:cachedmax=cached,MAXIMUM", - "VDEF:cachedavg=cached,AVERAGE", - "GPRINT:cachedmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:cachedmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:cachedavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:buffered#4499ff:%-15s" % _("Buffered data"), - "VDEF:bufferedmin=buffered,MINIMUM", - "VDEF:bufferedmax=buffered,MAXIMUM", - "VDEF:bufferedavg=buffered,AVERAGE", - "GPRINT:bufferedmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:bufferedmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:bufferedavg:%12s:" % _("Average") + " %6.2lf\n", - "STACK:free#7799ff:%-15s" % _("Free memory"), - "VDEF:freemin=free,MINIMUM", - "VDEF:freemax=free,MAXIMUM", - "VDEF:freeavg=free,AVERAGE", - "GPRINT:freemax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:freemin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:freeavg:%12s:" % _("Average") + " %6.2lf\n", - "LINE3:swap#ff0000:%-15s" % _("Used Swap space"), - "VDEF:swapmin=swap,MINIMUM", - "VDEF:swapmax=swap,MAXIMUM", - "VDEF:swapavg=swap,AVERAGE", - "GPRINT:swapmax:%12s:" % _("Maximum") + " %6.2lf" , - "GPRINT:swapmin:%12s:" % _("Minimum") + " %6.2lf", - "GPRINT:swapavg:%12s:" % _("Average") + " %6.2lf\n", ] - - def __init__(self, collecty, **kwargs): - Plugin.__init__(self, collecty, **kwargs) - - def collect(self): - ret = "%s" % self.time() - f = open("/proc/meminfo") - for line in f.readlines(): - if line.startswith("MemTotal:"): - total = float(line.split()[1]) - if line.startswith("MemFree:"): - free = float(line.split()[1]) - elif line.startswith("Buffers:"): - buffered = float(line.split()[1]) - elif line.startswith("Cached:"): - cached = float(line.split()[1]) - elif line.startswith("SwapTotal:"): - swapt = float(line.split()[1]) - elif line.startswith("SwapFree:"): - swapf = float(line.split()[1]) - - f.close() - - ret += ":%s" % ((total - (free + buffered + cached)) * 100 / total) - ret += ":%s" % (cached * 100 / total) - ret += ":%s" % (buffered * 100 / total) - ret += ":%s" % (free * 100 / total) - ret += ":%s" % ((swapt - swapf) * 100 / swapt) - - return ret - -register(PluginMem) +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import cpu +import loadavg +import memory + +registered_plugins = [ + cpu.PluginCPU, + loadavg.PluginLoadAvg, + memory.PluginMemory, +] diff --git a/collecty/plugins/base.py b/collecty/plugins/base.py new file mode 100644 index 0000000..472a3a0 --- /dev/null +++ b/collecty/plugins/base.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import logging +import os +import rrdtool +import threading +import time + +from ..constants import * +from ..i18n import _ + +class Plugin(threading.Thread): + # The name of this plugin. + name = None + + # A description for this plugin. + description = None + + # The schema of the RRD database. + rrd_schema = None + + # The default interval of this plugin. + default_interval = 60 + + def __init__(self, collecty, **kwargs): + threading.Thread.__init__(self, name=self.description) + self.daemon = True + + self.collecty = collecty + + # Check if this plugin was configured correctly. + assert self.name, "Name of the plugin is not set: %s" % self.name + assert self.description, "Description of the plugin is not set: %s" % self.description + assert self.rrd_schema + + # Initialize the logger. + self.log = logging.getLogger("collecty.plugins.%s" % self.name) + self.log.propagate = 1 + + # Keepalive options + self.heartbeat = 2 + self.running = True + + self.data = [] + + # Create the database file. + self.create() + + # Run some custom initialization. + self.init() + + self.log.info(_("Successfully initialized.")) + + def __repr__(self): + return "<Plugin %s>" % self.name + + def __str__(self): + return "Plugin %s %s" % (self.name, self.file) + + @property + def interval(self): + """ + Returns the interval in milliseconds, when the read method + should be called again. + """ + # XXX read this from the settings + + # Otherwise return the default. + return self.default_interval + + @property + def file(self): + """ + The absolute path to the RRD file of this plugin. + """ + return os.path.join(DATABASE_DIR, "%s.rrd" % self.name) + + def create(self): + """ + Creates an empty RRD file with the desired data structures. + """ + # Skip if the file does already exist. + if os.path.exists(self.file): + return + + dirname = os.path.dirname(self.file) + if not os.path.exists(dirname): + os.makedirs(dirname) + + rrdtool.create(self.file, *self.rrd_schema) + + self.log.debug(_("Created RRD file %s.") % self.file) + + def info(self): + return rrdtool.info(self.file) + + ### Basic methods + + def init(self): + """ + Do some custom initialization stuff here. + """ + pass + + def read(self): + """ + Gathers the statistical data, this plugin collects. + """ + raise NotImplementedError + + def submit(self): + """ + Flushes the read data to disk. + """ + # Do nothing in case there is no data to submit. + if not self.data: + return + + self.log.debug(_("Submitting data to database. %d entries.") % len(self.data)) + rrdtool.update(self.file, *self.data) + self.data = [] + + def _read(self, *args, **kwargs): + """ + This method catches errors from the read() method and logs them. + """ + try: + return self.read(*args, **kwargs) + + # Catch any exceptions, so collecty does not crash. + except Exception, e: + self.log.critical(_("Unhandled exception in read()!"), exc_info=True) + + def _submit(self, *args, **kwargs): + """ + This method catches errors from the submit() method and logs them. + """ + try: + return self.submit(*args, **kwargs) + + # Catch any exceptions, so collecty does not crash. + except Exception, e: + self.log.critical(_("Unhandled exception in submit()!"), exc_info=True) + + def run(self): + self.log.debug(_("Started.")) + + counter = 0 + while self.running: + if counter == 0: + self.log.debug(_("Collecting...")) + self._read() + + self.log.debug(_("Sleeping for %.4fs.") % self.interval) + + counter = self.interval / self.heartbeat + + time.sleep(self.heartbeat) + counter -= 1 + + self._submit() + self.log.debug(_("Stopped.")) + + def shutdown(self): + self.log.debug(_("Received shutdown signal.")) + self.running = False + + @property + def now(self): + """ + Returns the current timestamp in the UNIX timestamp format (UTC). + """ + return int(time.time()) + + def graph(self, file, interval=None): + args = [ "--imgformat", "PNG", + "-w", "580", # Width of the graph + "-h", "240", # Height of the graph + "--interlaced", "--slope-mode", ] + + intervals = { None : "-3h", + "hour" : "-1h", + "day" : "-25h", + "week" : "-360h" } + + args.append("--start") + if intervals.has_key(interval): + args.append(intervals[interval]) + else: + args.append(interval) + + info = { "file" : self.file } + for item in self._graph: + try: + args.append(item % info) + except TypeError: + args.append(item) + + rrdtool.graph(file, *args) diff --git a/collecty/plugins/cpu.py b/collecty/plugins/cpu.py new file mode 100644 index 0000000..bd21242 --- /dev/null +++ b/collecty/plugins/cpu.py @@ -0,0 +1,140 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +from __future__ import division + +import base + +from ..i18n import _ + +class PluginCPU(base.Plugin): + name = "cpu" + description = "CPU Usage Plugin" + + rrd_schema = [ + "DS:user:GAUGE:120:0:U", + "DS:nice:GAUGE:120:0:U", + "DS:sys:GAUGE:120:0:U", + "DS:idle:GAUGE:120:0:U", + "DS:wait:GAUGE:120:0:U", + "DS:irq:GAUGE:120:0:U", + "DS:sirq:GAUGE:120:0:U", + "RRA:AVERAGE:0.5:1:2160", + "RRA:AVERAGE:0.5:5:2016", + "RRA:AVERAGE:0.5:15:2880", + "RRA:AVERAGE:0.5:60:8760", + ] + + _graph = [ "DEF:user=%(file)s:user:AVERAGE", + "DEF:nice=%(file)s:nice:AVERAGE", + "DEF:sys=%(file)s:sys:AVERAGE", + "DEF:idle=%(file)s:idle:AVERAGE", + "DEF:wait=%(file)s:wait:AVERAGE", + "DEF:interrupt=%(file)s:interrupt:AVERAGE", + "AREA:user#ff0000:%-15s" % _("User"), + "VDEF:usermin=user,MINIMUM", + "VDEF:usermax=user,MAXIMUM", + "VDEF:useravg=user,AVERAGE", + "GPRINT:usermax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:usermin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:useravg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:nice#ff3300:%-15s" % _("Nice"), + "VDEF:nicemin=nice,MINIMUM", + "VDEF:nicemax=nice,MAXIMUM", + "VDEF:niceavg=nice,AVERAGE", + "GPRINT:nicemax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:nicemin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:niceavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:sys#ff6600:%-15s" % _("System"), + "VDEF:sysmin=sys,MINIMUM", + "VDEF:sysmax=sys,MAXIMUM", + "VDEF:sysavg=sys,AVERAGE", + "GPRINT:sysmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:sysmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:sysavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:wait#ff9900:%-15s" % _("Wait"), + "VDEF:waitmin=wait,MINIMUM", + "VDEF:waitmax=wait,MAXIMUM", + "VDEF:waitavg=wait,AVERAGE", + "GPRINT:waitmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:waitmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:waitavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:interrupt#ffcc00:%-15s" % _("Interrupt"), + "VDEF:interruptmin=interrupt,MINIMUM", + "VDEF:interruptmax=interrupt,MAXIMUM", + "VDEF:interruptavg=interrupt,AVERAGE", + "GPRINT:interruptmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:interruptmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:interruptavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:idle#ffff00:%-15s" % _("Idle"), + "VDEF:idlemin=idle,MINIMUM", + "VDEF:idlemax=idle,MAXIMUM", + "VDEF:idleavg=idle,AVERAGE", + "GPRINT:idlemax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:idlemin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:idleavg:%12s:" % _("Average") + " %6.2lf\n", ] + + @classmethod + def autocreate(cls, collecty, **kwargs): + # Every system has got at least one CPU. + return cls(collecty, **kwargs) + + def read(self): + """ + Reads the CPU usage. + """ + f = None + + try: + f = open("/proc/stat") + + for line in f.readlines(): + if not line.startswith("cpu"): + continue + + columns = line.split() + if len(columns) < 8: + continue + + entry = [ + columns[1], # user + columns[2], # nice + columns[3], # sys + columns[4], # idle + columns[5], # wait + columns[6], # irq + columns[7], # sirq + ] + + full = sum([int(e) for e in entry]) + + for i in range(len(entry)): + entry[i] = int(entry[i]) * 100 + entry[i] = "%s" % (entry[i] / full) + + entry.insert(0, "%s" % self.now) + + self.data.append(":".join(entry)) + break + + finally: + if f: + f.close() diff --git a/collecty/plugins/loadavg.py b/collecty/plugins/loadavg.py new file mode 100644 index 0000000..119c535 --- /dev/null +++ b/collecty/plugins/loadavg.py @@ -0,0 +1,78 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +import os + +import base + +from ..i18n import _ + +class PluginLoadAvg(base.Plugin): + name = "loadavg" + description = "Load Average Plugin" + + rrd_schema = [ + "DS:load1:GAUGE:120:0:U", + "DS:load5:GAUGE:120:0:U", + "DS:load15:GAUGE:120:0:U", + "RRA:AVERAGE:0.5:1:2160", + "RRA:AVERAGE:0.5:5:2016", + "RRA:AVERAGE:0.5:15:2880", + "RRA:AVERAGE:0.5:60:8760", + ] + + _graph = [ "DEF:load1=%(file)s:load1:AVERAGE", + "DEF:load5=%(file)s:load5:AVERAGE", + "DEF:load15=%(file)s:load15:AVERAGE", + "AREA:load1#ff0000:%s" % _("Load average 1m"), + "VDEF:load1min=load1,MINIMUM", + "VDEF:load1max=load1,MAXIMUM", + "VDEF:load1avg=load1,AVERAGE", + "GPRINT:load1max:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:load1min:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:load1avg:%12s:" % _("Average") + " %6.2lf\n", + "AREA:load5#ff9900:%s" % _("Load average 5m"), + "VDEF:load5min=load5,MINIMUM", + "VDEF:load5max=load5,MAXIMUM", + "VDEF:load5avg=load5,AVERAGE", + "GPRINT:load5max:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:load5min:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:load5avg:%12s:" % _("Average") + " %6.2lf\n", + "AREA:load15#ffff00:%s" % _("Load average 15m"), + "VDEF:load15min=load15,MINIMUM", + "VDEF:load15max=load15,MAXIMUM", + "VDEF:load15avg=load15,AVERAGE", + "GPRINT:load15max:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:load15min:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:load15avg:%12s:" % _("Average") + " %6.2lf\n", + "LINE:load5#dd8800", + "LINE:load1#dd0000", ] + + @classmethod + def autocreate(cls, collecty, **kwargs): + return cls(collecty, **kwargs) + + def read(self): + data = "%s" % self.now + for load in os.getloadavg(): + data += ":%s" % load + + self.data.append(data) diff --git a/collecty/plugins/memory.py b/collecty/plugins/memory.py new file mode 100644 index 0000000..2c20201 --- /dev/null +++ b/collecty/plugins/memory.py @@ -0,0 +1,124 @@ +#!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +############################################################################### + +from __future__ import division + +import base + +from ..i18n import _ + +class PluginMemory(base.Plugin): + name = "memory" + description = "Memory Usage Plugin" + + rrd_schema = [ + "DS:used:GAUGE:120:0:100", + "DS:cached:GAUGE:120:0:100", + "DS:buffered:GAUGE:120:0:100", + "DS:free:GAUGE:120:0:100", + "DS:swap:GAUGE:120:0:100", + "RRA:AVERAGE:0.5:1:2160", + "RRA:AVERAGE:0.5:5:2016", + "RRA:AVERAGE:0.5:15:2880", + "RRA:AVERAGE:0.5:60:8760", + ] + + _graph = [ "DEF:used=%(file)s:used:AVERAGE", + "DEF:cached=%(file)s:cached:AVERAGE", + "DEF:buffered=%(file)s:buffered:AVERAGE", + "DEF:free=%(file)s:free:AVERAGE", + "DEF:swap=%(file)s:swap:AVERAGE", + "AREA:used#0000ee:%-15s" % _("Used memory"), + "VDEF:usedmin=used,MINIMUM", + "VDEF:usedmax=used,MAXIMUM", + "VDEF:usedavg=used,AVERAGE", + "GPRINT:usedmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:usedmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:usedavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:cached#0099ee:%-15s" % _("Cached data"), + "VDEF:cachedmin=cached,MINIMUM", + "VDEF:cachedmax=cached,MAXIMUM", + "VDEF:cachedavg=cached,AVERAGE", + "GPRINT:cachedmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:cachedmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:cachedavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:buffered#4499ff:%-15s" % _("Buffered data"), + "VDEF:bufferedmin=buffered,MINIMUM", + "VDEF:bufferedmax=buffered,MAXIMUM", + "VDEF:bufferedavg=buffered,AVERAGE", + "GPRINT:bufferedmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:bufferedmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:bufferedavg:%12s:" % _("Average") + " %6.2lf\n", + "STACK:free#7799ff:%-15s" % _("Free memory"), + "VDEF:freemin=free,MINIMUM", + "VDEF:freemax=free,MAXIMUM", + "VDEF:freeavg=free,AVERAGE", + "GPRINT:freemax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:freemin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:freeavg:%12s:" % _("Average") + " %6.2lf\n", + "LINE3:swap#ff0000:%-15s" % _("Used Swap space"), + "VDEF:swapmin=swap,MINIMUM", + "VDEF:swapmax=swap,MAXIMUM", + "VDEF:swapavg=swap,AVERAGE", + "GPRINT:swapmax:%12s:" % _("Maximum") + " %6.2lf" , + "GPRINT:swapmin:%12s:" % _("Minimum") + " %6.2lf", + "GPRINT:swapavg:%12s:" % _("Average") + " %6.2lf\n", ] + + @classmethod + def autocreate(cls, collecty, **kwargs): + # Every system has got memory. + return cls(collecty, **kwargs) + + def read(self): + f = None + + try: + ret = "%s" % self.now + + f = open("/proc/meminfo") + for line in f.readlines(): + if line.startswith("MemTotal:"): + total = float(line.split()[1]) + if line.startswith("MemFree:"): + free = float(line.split()[1]) + elif line.startswith("Buffers:"): + buffered = float(line.split()[1]) + elif line.startswith("Cached:"): + cached = float(line.split()[1]) + elif line.startswith("SwapTotal:"): + swapt = float(line.split()[1]) + elif line.startswith("SwapFree:"): + swapf = float(line.split()[1]) + + ret += ":%s" % ((total - (free + buffered + cached)) * 100 / total) + ret += ":%s" % (cached * 100 / total) + ret += ":%s" % (buffered * 100 / total) + ret += ":%s" % (free * 100 / total) + + if swapt: + ret += ":%s" % ((swapt - swapf) * 100 / swapt) + else: + ret += ":0" + + self.data.append(ret) + finally: + if f: + f.close() diff --git a/collectyd b/collectyd index a67dc36..ae0f608 100755 --- a/collectyd +++ b/collectyd @@ -1,35 +1,39 @@ #!/usr/bin/python +############################################################################### +# # +# collecty - A system statistics collection daemon for IPFire # +# Copyright (C) 2012 IPFire development team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see http://www.gnu.org/licenses/. # +# # +###############################################################################
-import os -import sys - -import daemon import optparse +import sys
import collecty
-c = collecty.Collecty() - -# Parse command line options +# Parse command line options. op = optparse.OptionParser(usage="usage: %prog [options] <configfile1> ... <configfileN>") op.add_option("-d", "--daemon", action="store_true", default=False, help="Run as a daemon in background.") (options, configfiles) = op.parse_args()
-if configfiles: - for file in configfiles: - c.read_config(file) -else: - # Load default config file - c.read_config("/etc/collecty/collecty.conf") - -if not c.instances: - print >>sys.stderr, "Error: No instances were configured." - sys.exit(1) +# Initialize the application. +c = collecty.Collecty()
-if options.daemon: - with daemon.DaemonContext(stdout=sys.stdout, stderr=sys.stderr): - c.run() -else: - c.run() +# Run. +c.run()
+sys.exit(0)
hooks/post-receive -- Statistics collection daemon.