]> git.ipfire.org Git - oddments/collecty.git/commitdiff
Add CollectyClient to generate graph images.
authorMichael Tremer <michael.tremer@ipfire.org>
Sat, 25 Aug 2012 11:22:31 +0000 (11:22 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Sat, 25 Aug 2012 11:22:31 +0000 (11:22 +0000)
13 files changed:
collecty-client [new file with mode: 0755]
collecty/__init__.py
collecty/client.py [new file with mode: 0644]
collecty/constants.py
collecty/daemon.py [new file with mode: 0644]
collecty/errors.py [new file with mode: 0644]
collecty/plugins/base.py
collecty/plugins/cpu.py
collecty/plugins/entropy.py
collecty/plugins/loadavg.py
collecty/plugins/memory.py
collectyd
po/collecty.pot

diff --git a/collecty-client b/collecty-client
new file mode 100755 (executable)
index 0000000..9251bf8
--- /dev/null
@@ -0,0 +1,28 @@
+#!/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 collecty
+
+client = collecty.CollectyClient()
+
+for type in ("cpu", "entropy", "memory", "loadavg"):
+       for interval in ("day", "week", "year"):
+               client.graph(type, "%s-%s.png" % (type, interval), interval)
index afae3f5fb45a312a28bb1bbabaff8a89331fd478..187f6ab65a24a77d3216a6c7d1f86687c803cc92 100644 (file)
 #                                                                             #
 ###############################################################################
 
-import signal
-import time
-
-import ConfigParser as configparser
-
-import plugins
-
-from i18n import _
-
 # Initialize logging.
 import logging
 log = logging.getLogger("collecty")
@@ -40,126 +31,5 @@ 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
-
-       def __init__(self):
-               self.config = configparser.ConfigParser()
-               self.instances = []
-
-               # Indicates whether this process should be running or not.
-               self.running = True
-               self.timer = plugins.Timer(self.SUBMIT_INTERVAL, heartbeat=2)
-
-               # 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)
-               
-               for section in self.config.sections():
-                       try:
-                               plugin = self.config.get(section, "plugin")
-                               plugin = plugins.find(plugin)
-                       except configparser.NoOptionError:
-                               raise ConfigError, "Syntax error in configuration: plugin option is missing."
-                       except:
-                               raise Exception, "Plugin configuration error: Maybe plugin wasn't found? %s" % plugin
-
-                       kwargs = {}
-                       for (key, value) in self.config.items(section):
-                               if key == "plugin":
-                                       continue
-
-                               kwargs[key] = value
-                       kwargs["file"] = section
-
-                       i = plugin(self, **kwargs)
-                       self.instances.append(i)
-
-       def run(self):
-               # Register signal handlers.
-               self.register_signal_handler()
-
-               # Start all plugin instances.
-               for i in self.instances:
-                       i.start()
-
-               # Regularly submit all data to disk.
-               while self.running:
-                       if self.timer.wait():
-                               self.submit_all()
-
-               # 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.
-               """
-               log.debug(_("Submitting all data in memory"))
-               for i in self.instances:
-                       i._submit()
-
-               # Schedule the next submit.
-               self.timer.reset()
-
-       def shutdown(self):
-               log.debug(_("Received shutdown signal"))
-
-               self.running = False
-               if self.timer:
-                       self.timer.cancel()
-
-               # Propagating shutdown to all threads.
-               for i in self.instances:
-                       i.shutdown()
-
-       def register_signal_handler(self):
-               for s in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
-                       log.debug(_("Registering signal %d") % s)
-
-                       signal.signal(s, self.signal_handler)
-
-       def signal_handler(self, sig, *args, **kwargs):
-               log.info(_("Caught signal %d") % sig)
-
-               if sig in (signal.SIGTERM, signal.SIGINT):
-                       # Shutdown this application.
-                       self.shutdown()
-
-               elif sig == signal.SIGUSR1:
-                       # Submit all data.
-                       self.submit_all()
+from client import CollectyClient
+from daemon import Collecty
diff --git a/collecty/client.py b/collecty/client.py
new file mode 100644 (file)
index 0000000..9ac640b
--- /dev/null
@@ -0,0 +1,46 @@
+#!/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 daemon
+
+import logging
+log = logging.getLogger("collectly.client")
+
+class CollectyClient(object):
+       def __init__(self, **settings):
+               self.collecty = daemon.Collecty(**settings)
+
+       @property
+       def instances(self):
+               return self.collecty.instances
+
+       def get_instance_by_id(self, id):
+               for instance in self.instances:
+                       if not instance.id == id:
+                               continue
+
+                       return instance
+
+       def graph(self, id, filename, interval=None, **kwargs):
+               instance = self.get_instance_by_id(id)
+               assert instance, "Could not find instance: %s" % id
+
+               instance.graph(filename, interval=interval, **kwargs)
index 969a083081039361f44b29b8dfb366cc97168d21..20bdc351fa09b023170c363976195de0c594e4ac 100644 (file)
 #                                                                             #
 ###############################################################################
 
+from i18n import _
+
 DATABASE_DIR = "/var/lib/collecty"
+
+GRAPH_DEFAULT_ARGUMENTS = [
+       # Always generate graphs in PNG format.
+       "--imgformat", "PNG",
+
+       # Disable the border around the image.
+       "--border", "0",
+
+       # Let's width and height define the size of
+       # the entire image.
+       "--full-size-mode",
+
+       # Gives the curves a more organic look.
+       "--slope-mode",
+
+       # Show nicer labels.
+       "--dynamic-labels",
+
+       # Brand all generated graphs.
+       "--watermark", _("Created by collecty"),
+]
+
+GRAPH_DEFAULT_WIDTH = 768
+GRAPH_DEFAULT_HEIGHT = 480
diff --git a/collecty/daemon.py b/collecty/daemon.py
new file mode 100644 (file)
index 0000000..c736364
--- /dev/null
@@ -0,0 +1,157 @@
+#!/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 ConfigParser as configparser
+
+import plugins
+
+from constants import *
+from i18n import _
+
+import logging
+log = logging.getLogger("collecty")
+
+class Collecty(object):
+       # The default interval, when all data is written to disk.
+       SUBMIT_INTERVAL = 300
+
+       def __init__(self, debug=False):
+               self.config = configparser.ConfigParser()
+               self.instances = []
+
+               # Indicates whether this process should be running or not.
+               self.running = True
+               self.timer = plugins.Timer(self.SUBMIT_INTERVAL, heartbeat=2)
+
+               # 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)
+               
+               for section in self.config.sections():
+                       try:
+                               plugin = self.config.get(section, "plugin")
+                               plugin = plugins.find(plugin)
+                       except configparser.NoOptionError:
+                               raise ConfigError, "Syntax error in configuration: plugin option is missing."
+                       except:
+                               raise Exception, "Plugin configuration error: Maybe plugin wasn't found? %s" % plugin
+
+                       kwargs = {}
+                       for (key, value) in self.config.items(section):
+                               if key == "plugin":
+                                       continue
+
+                       kwargs[key] = value
+                       kwargs["file"] = section
+
+                       i = plugin(self, **kwargs)
+                       self.instances.append(i)
+
+       def run(self):
+               # Register signal handlers.
+               self.register_signal_handler()
+
+               # Start all plugin instances.
+               for i in self.instances:
+                       i.start()
+
+               # Regularly submit all data to disk.
+               while self.running:
+                       if self.timer.wait():
+                               self.submit_all()
+
+               # 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.
+               """
+               log.debug(_("Submitting all data in memory"))
+               for i in self.instances:
+                       i._submit()
+
+               # Schedule the next submit.
+               self.timer.reset()
+
+       def shutdown(self):
+               log.debug(_("Received shutdown signal"))
+
+               self.running = False
+               if self.timer:
+                       self.timer.cancel()
+
+               # Propagating shutdown to all threads.
+               for i in self.instances:
+                       i.shutdown()
+
+       def register_signal_handler(self):
+               for s in (signal.SIGTERM, signal.SIGINT, signal.SIGUSR1):
+                       log.debug(_("Registering signal %d") % s)
+
+               signal.signal(s, self.signal_handler)
+
+       def signal_handler(self, sig, *args, **kwargs):
+               log.info(_("Caught signal %d") % sig)
+
+               if sig in (signal.SIGTERM, signal.SIGINT):
+                       # Shutdown this application.
+                       self.shutdown()
+
+               elif sig == signal.SIGUSR1:
+                       # Submit all data.
+                       self.submit_all()
+
+       @property
+       def graph_default_arguments(self):
+               return GRAPH_DEFAULT_ARGUMENTS
diff --git a/collecty/errors.py b/collecty/errors.py
new file mode 100644 (file)
index 0000000..084af78
--- /dev/null
@@ -0,0 +1,26 @@
+#!/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/>.       #
+#                                                                             #
+###############################################################################
+
+class CollectyError(Exception):
+       pass
+
+class ConfigError(CollectyError):
+       pass
index e85653ea64779ce941debe9f8bb4128e874f3798..76ac1ccd3c602a6e14726377dc4b3f94da509ffe 100644 (file)
@@ -66,6 +66,12 @@ class Plugin(threading.Thread):
        # The schema of the RRD database.
        rrd_schema = None
 
+       # Instructions how to create the graph.
+       rrd_graph = None
+
+       # Extra arguments passed to rrdgraph.
+       rrd_graph_args = []
+
        # The default interval of this plugin.
        default_interval = 60
 
@@ -104,6 +110,13 @@ class Plugin(threading.Thread):
        def __str__(self):
                return "Plugin %s %s" % (self.name, self.file)
 
+       @property
+       def id(self):
+               """
+                       A unique ID of the plugin instance.
+               """
+               return self.name
+
        @property
        def interval(self):
                """
@@ -219,16 +232,23 @@ class Plugin(threading.Thread):
                """
                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", ]
+       def graph(self, file, interval=None,
+                       width=GRAPH_DEFAULT_WIDTH, height=GRAPH_DEFAULT_HEIGHT):
+
+               args = [
+                       "--width", "%d" % width,
+                       "--height", "%d" % height,
+               ]
+               args += self.collecty.graph_default_arguments
+               args += self.rrd_graph_args
 
-               intervals = { None   : "-3h",
-                                       "hour" : "-1h",
-                                       "day"  : "-25h",
-                                       "week" : "-360h" }
+               intervals = {
+                       None   : "-3h",
+                       "hour" : "-1h",
+                       "day"  : "-25h",
+                       "week" : "-360h",
+                       "year" : "-365d",
+               }
 
                args.append("--start")
                if intervals.has_key(interval):
@@ -237,10 +257,10 @@ class Plugin(threading.Thread):
                        args.append(interval)
 
                info = { "file" : self.file }
-               for item in self._graph:
+               for item in self.rrd_graph:
                        try:
                                args.append(item % info)
                        except TypeError:
                                args.append(item)
 
-                       rrdtool.graph(file, *args)
+               rrdtool.graph(file, *args)
index bd2124252728e21c2a6ed9a0a414cef8a294d6ce..fc95fda648fd18bf7f9dbf01fe5bd7392b33f439 100644 (file)
@@ -43,54 +43,79 @@ class PluginCPU(base.Plugin):
                "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", ]
+       rrd_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:irq=%(file)s:irq:AVERAGE",
+               "DEF:sirq=%(file)s:sirq:AVERAGE",
+
+               "AREA:user#90EE90:%-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#4169E1:%-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#DC143C:%-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#483D8B:%-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:irq#DAA520:%-15s" % _("Interrupt"),
+               "VDEF:irqmin=irq,MINIMUM",
+               "VDEF:irqmax=irq,MAXIMUM",
+               "VDEF:irqavg=irq,AVERAGE",
+               "GPRINT:irqmax:%12s\:" % _("Maximum") + " %6.2lf" ,
+               "GPRINT:irqmin:%12s\:" % _("Minimum") + " %6.2lf",
+               "GPRINT:irqavg:%12s\:" % _("Average") + " %6.2lf\\n",
+
+               "STACK:sirq#FFD700:%-15s" % _("Soft interrupt"),
+               "VDEF:sirqmin=sirq,MINIMUM",
+               "VDEF:sirqmax=sirq,MAXIMUM",
+               "VDEF:sirqavg=sirq,AVERAGE",
+               "GPRINT:sirqmax:%12s\:" % _("Maximum") + " %6.2lf" ,
+               "GPRINT:sirqmin:%12s\:" % _("Minimum") + " %6.2lf",
+               "GPRINT:sirqavg:%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",
+       ]
+
+       rrd_graph_args = [
+               "--title", _("CPU usage"),
+               "--vertical-label", _("Percent"),
+
+               # This can never be more than 100 percent.
+               #"--lower-limit", "0", "--upper-limit", "100",
+       ]
 
        @classmethod
        def autocreate(cls, collecty, **kwargs):
index 5aa77ec072394fa02235c6ebaf9f82c10cbc3e8f..7d588d605ec547c4875c68462fe09ec13aad7a09 100644 (file)
@@ -23,6 +23,8 @@ import os
 
 import base
 
+from ..i18n import _
+
 ENTROPY_FILE = "/proc/sys/kernel/random/entropy_avail"
 
 class PluginEntropy(base.Plugin):
@@ -37,6 +39,27 @@ class PluginEntropy(base.Plugin):
                "RRA:AVERAGE:0.5:60:8760",
        ]
 
+       rrd_graph = [
+               "DEF:entropy=%(file)s:entropy:AVERAGE",
+               "CDEF:entropytrend=entropy,43200,TREND",
+
+               "LINE3:entropy#ff0000:%-15s" % _("Available entropy"),
+               "VDEF:entrmin=entropy,MINIMUM",
+               "VDEF:entrmax=entropy,MAXIMUM",
+               "VDEF:entravg=entropy,AVERAGE",
+               "GPRINT:entrmax:%12s\:" % _("Maximum") + " %5.0lf",
+               "GPRINT:entrmin:%12s\:" % _("Minimum") + " %5.0lf",
+               "GPRINT:entravg:%12s\:" % _("Average") + " %5.0lf\\n",
+
+               "LINE3:entropytrend#000000",
+       ]
+       rrd_graph_args = [
+               "--title", _("Available entropy"),
+               "--vertical-label", _("Bits"),
+
+               "--lower-limit", "0", "--rigid",
+       ]
+
        @classmethod
        def autocreate(cls, collecty, **kwargs):
                if not os.path.exists(ENTROPY_FILE):
index 119c535dc7d60a8e1b83f22be3d846434752a2da..9b56e13c385e15712fc7205fc0733c2feec98207 100644 (file)
@@ -39,32 +39,44 @@ class PluginLoadAvg(base.Plugin):
                "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", ]
+       rrd_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",
+       ]
+       rrd_graph_args = [
+               "--title", _("Load average"),
+               "--vertical-label", _("Load"),
+
+               "--lower-limit", "0", "--rigid",
+       ]
 
        @classmethod
        def autocreate(cls, collecty, **kwargs):
index 2c20201604549ed0dfb92fea9602207064684e20..952442a60233a73c26d2a3ffcb01a88133e33a0a 100644 (file)
@@ -41,46 +41,62 @@ class PluginMemory(base.Plugin):
                "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", ]
+       rrd_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#90EE90:%-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:buffered#4169E1:%-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:cached#FFD700:%-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: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",
+       ]
+       rrd_graph_args = [
+               "--title", _("Memory Usage"),
+               "--vertical-label", _("Percent"),
+
+               # Limit y axis.
+               "--upper-limit", "100",
+               "--lower-limit", "0",
+               "--rigid",
+       ]
 
        @classmethod
        def autocreate(cls, collecty, **kwargs):
index a90e5ded4623576d482e87069202e492d5c2eb7a..32caafa064e8e9aa76df824b1e1565406bed74c1 100755 (executable)
--- a/collectyd
+++ b/collectyd
@@ -32,7 +32,7 @@ op.add_option("-d", "--debug", action="store_true", default=False,
 
 # Initialize the settings for this Collecty instance.
 settings = {
-       debug : options.debug,
+       "debug" : options.debug,
 }
 
 # Initialize the application.
index 4cf4a7d7464fa07e9df387648d54d9608b132ee6..67e180c2cec110972283e08b6488c719c672ccdf 100644 (file)
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2012-08-08 08:55+0000\n"
+"POT-Creation-Date: 2012-08-25 11:22+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,166 +17,202 @@ msgstr ""
 "Content-Type: text/plain; charset=CHARSET\n"
 "Content-Transfer-Encoding: 8bit\n"
 
-#: collecty/__init__.py:61
+#. Brand all generated graphs.
+#: collecty/constants.py:44
+msgid "Created by collecty"
+msgstr ""
+
+#: collecty/daemon.py:49
 msgid "Collecty successfully initialized."
 msgstr ""
 
-#: collecty/__init__.py:75
+#: collecty/daemon.py:63
 #, python-format
 msgid "Plugin '%(name)s' registered %(number)s instance(s)."
 msgstr ""
 
-#: collecty/__init__.py:120
+#: collecty/daemon.py:108
 #, python-format
 msgid "%s is not alive anymore. Removing."
 msgstr ""
 
-#: collecty/__init__.py:126
+#: collecty/daemon.py:114
 msgid "No thread running. Exiting main thread."
 msgstr ""
 
-#: collecty/__init__.py:132
+#: collecty/daemon.py:120
 msgid "Submitting all data in memory"
 msgstr ""
 
-#: collecty/__init__.py:140
+#: collecty/daemon.py:128
 msgid "Received shutdown signal"
 msgstr ""
 
-#: collecty/__init__.py:152
+#: collecty/daemon.py:140
 #, python-format
 msgid "Registering signal %d"
 msgstr ""
 
-#: collecty/__init__.py:157
+#: collecty/daemon.py:145
 #, python-format
 msgid "Caught signal %d"
 msgstr ""
 
-#: collecty/plugins/base.py:99
+#: collecty/plugins/base.py:105
 msgid "Successfully initialized."
 msgstr ""
 
-#: collecty/plugins/base.py:139
+#: collecty/plugins/base.py:152
 #, python-format
 msgid "Created RRD file %s."
 msgstr ""
 
-#: collecty/plugins/base.py:166
+#: collecty/plugins/base.py:179
 #, python-format
 msgid "Submitting data to database. %d entries."
 msgstr ""
 
-#: collecty/plugins/base.py:179
+#: collecty/plugins/base.py:192
 msgid "Unhandled exception in read()!"
 msgstr ""
 
-#: collecty/plugins/base.py:190
+#: collecty/plugins/base.py:203
 msgid "Unhandled exception in submit()!"
 msgstr ""
 
-#: collecty/plugins/base.py:193
+#: collecty/plugins/base.py:206
 msgid "Started."
 msgstr ""
 
-#: collecty/plugins/base.py:201
+#: collecty/plugins/base.py:214
 msgid "Collecting..."
 msgstr ""
 
-#: collecty/plugins/base.py:205
+#: collecty/plugins/base.py:218
 msgid "Stopped."
 msgstr ""
 
-#: collecty/plugins/base.py:208
+#: collecty/plugins/base.py:221
 msgid "Received shutdown signal."
 msgstr ""
 
-#: collecty/plugins/cpu.py:52
+#: collecty/plugins/cpu.py:55
 msgid "User"
 msgstr ""
 
-#: collecty/plugins/cpu.py:56 collecty/plugins/cpu.py:63
-#: collecty/plugins/cpu.py:70 collecty/plugins/cpu.py:77
-#: collecty/plugins/cpu.py:84 collecty/plugins/cpu.py:91
-#: collecty/plugins/loadavg.py:49 collecty/plugins/loadavg.py:56
-#: collecty/plugins/loadavg.py:63 collecty/plugins/memory.py:53
-#: collecty/plugins/memory.py:60 collecty/plugins/memory.py:67
-#: collecty/plugins/memory.py:74 collecty/plugins/memory.py:81
+#: collecty/plugins/cpu.py:59 collecty/plugins/cpu.py:67
+#: collecty/plugins/cpu.py:75 collecty/plugins/cpu.py:83
+#: collecty/plugins/cpu.py:91 collecty/plugins/cpu.py:99
+#: collecty/plugins/entropy.py:50 collecty/plugins/loadavg.py:51
+#: collecty/plugins/loadavg.py:59 collecty/plugins/loadavg.py:67
+#: collecty/plugins/memory.py:55 collecty/plugins/memory.py:63
+#: collecty/plugins/memory.py:71 collecty/plugins/memory.py:87
 msgid "Maximum"
 msgstr ""
 
-#: collecty/plugins/cpu.py:57 collecty/plugins/cpu.py:64
-#: collecty/plugins/cpu.py:71 collecty/plugins/cpu.py:78
-#: collecty/plugins/cpu.py:85 collecty/plugins/cpu.py:92
-#: collecty/plugins/loadavg.py:50 collecty/plugins/loadavg.py:57
-#: collecty/plugins/loadavg.py:64 collecty/plugins/memory.py:54
-#: collecty/plugins/memory.py:61 collecty/plugins/memory.py:68
-#: collecty/plugins/memory.py:75 collecty/plugins/memory.py:82
+#: collecty/plugins/cpu.py:60 collecty/plugins/cpu.py:68
+#: collecty/plugins/cpu.py:76 collecty/plugins/cpu.py:84
+#: collecty/plugins/cpu.py:92 collecty/plugins/cpu.py:100
+#: collecty/plugins/entropy.py:51 collecty/plugins/loadavg.py:52
+#: collecty/plugins/loadavg.py:60 collecty/plugins/loadavg.py:68
+#: collecty/plugins/memory.py:56 collecty/plugins/memory.py:64
+#: collecty/plugins/memory.py:72 collecty/plugins/memory.py:88
 msgid "Minimum"
 msgstr ""
 
-#: collecty/plugins/cpu.py:58 collecty/plugins/cpu.py:65
-#: collecty/plugins/cpu.py:72 collecty/plugins/cpu.py:79
-#: collecty/plugins/cpu.py:86 collecty/plugins/cpu.py:93
-#: collecty/plugins/loadavg.py:51 collecty/plugins/loadavg.py:58
-#: collecty/plugins/loadavg.py:65 collecty/plugins/memory.py:55
-#: collecty/plugins/memory.py:62 collecty/plugins/memory.py:69
-#: collecty/plugins/memory.py:76 collecty/plugins/memory.py:83
+#: collecty/plugins/cpu.py:61 collecty/plugins/cpu.py:69
+#: collecty/plugins/cpu.py:77 collecty/plugins/cpu.py:85
+#: collecty/plugins/cpu.py:93 collecty/plugins/cpu.py:101
+#: collecty/plugins/entropy.py:52 collecty/plugins/loadavg.py:53
+#: collecty/plugins/loadavg.py:61 collecty/plugins/loadavg.py:69
+#: collecty/plugins/memory.py:57 collecty/plugins/memory.py:65
+#: collecty/plugins/memory.py:73 collecty/plugins/memory.py:89
 msgid "Average"
 msgstr ""
 
-#: collecty/plugins/cpu.py:59
+#: collecty/plugins/cpu.py:63
 msgid "Nice"
 msgstr ""
 
-#: collecty/plugins/cpu.py:66
+#: collecty/plugins/cpu.py:71
 msgid "System"
 msgstr ""
 
-#: collecty/plugins/cpu.py:73
+#: collecty/plugins/cpu.py:79
 msgid "Wait"
 msgstr ""
 
-#: collecty/plugins/cpu.py:80
+#: collecty/plugins/cpu.py:87
 msgid "Interrupt"
 msgstr ""
 
-#: collecty/plugins/cpu.py:87
-msgid "Idle"
+#: collecty/plugins/cpu.py:95
+msgid "Soft interrupt"
+msgstr ""
+
+#: collecty/plugins/cpu.py:113
+msgid "CPU usage"
+msgstr ""
+
+#: collecty/plugins/cpu.py:114 collecty/plugins/memory.py:93
+msgid "Percent"
+msgstr ""
+
+#: collecty/plugins/entropy.py:46 collecty/plugins/entropy.py:57
+msgid "Available entropy"
+msgstr ""
+
+#: collecty/plugins/entropy.py:58
+msgid "Bits"
 msgstr ""
 
-#: collecty/plugins/entropy.py:43
+#: collecty/plugins/entropy.py:66
 msgid "Entropy kernel interface does not exist."
 msgstr ""
 
-#: collecty/plugins/loadavg.py:45
+#: collecty/plugins/loadavg.py:47
 msgid "Load average  1m"
 msgstr ""
 
-#: collecty/plugins/loadavg.py:52
+#: collecty/plugins/loadavg.py:55
 msgid "Load average  5m"
 msgstr ""
 
-#: collecty/plugins/loadavg.py:59
+#: collecty/plugins/loadavg.py:63
 msgid "Load average 15m"
 msgstr ""
 
-#: collecty/plugins/memory.py:49
-msgid "Used memory"
+#: collecty/plugins/loadavg.py:75
+msgid "Load average"
 msgstr ""
 
-#: collecty/plugins/memory.py:56
-msgid "Cached data"
+#: collecty/plugins/loadavg.py:76
+msgid "Load"
 msgstr ""
 
-#: collecty/plugins/memory.py:63
+#: collecty/plugins/memory.py:51
+msgid "Used memory"
+msgstr ""
+
+#: collecty/plugins/memory.py:59
 msgid "Buffered data"
 msgstr ""
 
-#: collecty/plugins/memory.py:70
-msgid "Free memory"
+#: collecty/plugins/memory.py:67
+msgid "Cached data"
 msgstr ""
 
-#: collecty/plugins/memory.py:77
+#. "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",
+#: collecty/plugins/memory.py:83
 msgid "Used Swap space"
 msgstr ""
+
+#: collecty/plugins/memory.py:92
+msgid "Memory Usage"
+msgstr ""