Beispiel #1
0
    def update(node, conf, archive, *values, **kwargs):
        """
    Updates an existing RRD archive or creates a new one if needed.
    """
        if not os.path.isfile(archive):
            RRA.create(conf, archive)

        nvalues = []
        for idx, value in enumerate(values):
            if value is None:
                nvalues.append("U")
            else:
                nvalues.append(str(value))

        rrdtool.update(archive, "{0}:{1}".format(kwargs.get("timestamp", "N"), ":".join(nvalues)))

        # Record data in database store if set
        data = {}
        for i, x in enumerate(conf.sources):
            data[x.name] = values[i]

        now = time.time()
        if "db_model" in conf.__dict__ and now - conf.last_update >= conf.interval:
            try:
                m = conf.db_model(**data)
                m.node = node
                m.timestamp = datetime.now()
                m.save()
                conf.last_update = now
            except ValueError:
                pass

        # Record data in archive when available
        if kwargs.get("graph") is not None:
            data_archive.record_data(kwargs.get("graph"), datetime.now(), data)
Beispiel #2
0
    def update(node, conf, archive, *values, **kwargs):
        """
    Updates an existing RRD archive or creates a new one if needed.
    """
        if not os.path.isfile(archive):
            RRA.create(conf, archive)

        nvalues = []
        for idx, value in enumerate(values):
            if value is None:
                nvalues.append("U")
            else:
                nvalues.append(str(value))

        rrdtool.update(
            archive, "{0}:{1}".format(kwargs.get("timestamp", "N"),
                                      ":".join(nvalues)))

        # Record data in database store if set
        data = {}
        for i, x in enumerate(conf.sources):
            data[x.name] = values[i]

        now = time.time()
        if 'db_model' in conf.__dict__ and now - conf.last_update >= conf.interval:
            try:
                m = conf.db_model(**data)
                m.node = node
                m.timestamp = datetime.now()
                m.save()
                conf.last_update = now
            except ValueError:
                pass

        # Record data in archive when available
        if kwargs.get('graph') is not None:
            data_archive.record_data(kwargs.get('graph'), datetime.now(), data)
Beispiel #3
0
    def convert(conf, archive, action="refresh", opts="", graph=None):
        """
    Converts 
    """
        try:
            os.stat(archive)
        except OSError:
            return

        # Dump the archive into XML form
        process = subprocess.Popen(['/usr/bin/rrdtool', 'dump', archive],
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

        # Make any transformations needed
        xml = ElementTree.parse(process.stdout)
        wanted_source_names = [source.name for source in conf.sources]
        start_offset = 5  # version, step, seconds comment, lastupdate, timestamp comment
        changed = False

        if action == "refresh":
            # A simple marker to rescan sources as something has been modified
            class RescanSources(Exception):
                pass

            while True:
                data_sources = xml.findall('/ds')
                data_source_names = [
                    source.findtext('./name').strip()
                    for source in data_sources
                ]

                try:
                    for ds in data_source_names:
                        if ds not in wanted_source_names:
                            print "WARNING: Removal of sources currently not supported!"
                            # If we remove something we must continue the loop
                            # raise RescanSources

                    for idx, ds in enumerate(wanted_source_names):
                        if ds not in data_source_names:
                            print "INFO: Adding data source '%s' to RRD '%s.'" % (
                                ds, os.path.basename(archive))

                            # Update header
                            dse = ElementTree.Element("ds")
                            ElementTree.SubElement(
                                dse, "name").text = conf.sources[idx].name
                            ElementTree.SubElement(
                                dse, "type").text = conf.sources[idx].type
                            ElementTree.SubElement(
                                dse, "minimal_heartbeat").text = str(
                                    conf.sources[idx].heartbeat)
                            ElementTree.SubElement(dse, "min").text = "NaN"
                            ElementTree.SubElement(dse, "max").text = "NaN"
                            ElementTree.SubElement(dse,
                                                   "last_ds").text = "UNKN"
                            ElementTree.SubElement(
                                dse, "value").text = "0.0000000000e+00"
                            ElementTree.SubElement(dse,
                                                   "unknown_sec").text = "0"
                            xml.getroot().insert(start_offset + idx, dse)

                            # Update all RRAs
                            for rra in xml.findall('/rra'):
                                # Update RRA header
                                cdp = rra.find('./cdp_prep')
                                dse = ElementTree.Element("ds")
                                ElementTree.SubElement(
                                    dse, "primary_value").text = "NaN"
                                ElementTree.SubElement(
                                    dse, "secondary_value").text = "NaN"
                                ElementTree.SubElement(dse,
                                                       "value").text = "NaN"
                                ElementTree.SubElement(
                                    dse, "unknown_datapoints").text = "0"
                                cdp.insert(idx, dse)

                                # Update all RRA datapoints
                                for row in rra.findall('./database/row'):
                                    v = ElementTree.Element("v")
                                    v.text = "NaN"
                                    row.insert(idx, v)

                            changed = True

                    break
                except RescanSources:
                    pass

            # Add RRAs when they have changed (only addition is supported)
            for wanted_rra in conf.archives:
                for rra in xml.findall('/rra'):
                    cf = rra.findtext('./cf').strip()
                    steps = int(rra.findtext('./pdp_per_row').strip())
                    rows = len(rra.findall('./database/row'))
                    xff = float(rra.findtext('./params/xff').strip())

                    match = all([
                        cf == wanted_rra.cf, steps == wanted_rra.steps,
                        rows == wanted_rra.rows, xff == wanted_rra.xff
                    ])

                    if match:
                        break
                else:
                    # Not found in existing RRAs, we need to add it
                    print "INFO: Adding new RRA '%s' to '%s'." % (
                        wanted_rra, os.path.basename(archive))
                    changed = True

                    rra = ElementTree.SubElement(xml.getroot(), "rra")
                    ElementTree.SubElement(rra, "cf").text = wanted_rra.cf
                    ElementTree.SubElement(rra, "pdp_per_row").text = str(
                        wanted_rra.steps)
                    params = ElementTree.SubElement(rra, "params")
                    ElementTree.SubElement(params,
                                           "xff").text = str(wanted_rra.xff)
                    cdp_prep = ElementTree.SubElement(rra, "cdp_prep")

                    for ds in conf.sources:
                        dse = ElementTree.SubElement(cdp_prep, "ds")
                        ElementTree.SubElement(dse,
                                               "primary_value").text = "NaN"
                        ElementTree.SubElement(dse,
                                               "secondary_value").text = "NaN"
                        ElementTree.SubElement(dse, "value").text = "NaN"
                        ElementTree.SubElement(dse,
                                               "unknown_datapoints").text = "0"

                    database = ElementTree.SubElement(rra, "database")
                    for row in xrange(wanted_rra.rows):
                        row = ElementTree.SubElement(database, "row")
                        for v in xrange(len(conf.sources)):
                            ElementTree.SubElement(row, "v").text = "NaN"
        elif action == "archive":
            # Archives data from RRDs
            print "INFO: Archiving RRD '%s.'" % os.path.basename(archive)

            for rra in xml.findall('/rra'):
                cf = rra.findtext('./cf').strip()
                if cf == 'AVERAGE':
                    timestamp = None

                    for item in rra.findall('./database')[0]:
                        if item.tag == "row":
                            # Data
                            values = [
                                x.text.strip() for x in item.findall('./v')
                            ]
                            data = {}
                            for i, x in enumerate(conf.sources):
                                data[x.name] = values[i]

                            if graph is not None:
                                data_archive.record_data(
                                    graph, timestamp, data)
                        else:
                            # Comment
                            timestamp = datetime.fromtimestamp(
                                int(item.text.split("/")[1].strip()))
        elif action == "switch_sources":
            # Switches two data sources
            try:
                ar_name, ds1, ds2 = opts.split(',')
            except ValueError:
                print "ERROR: Invalid RRD update options for action switch_sources!"
                print "USAGE: <archive_class>,<ds1>,<ds2>"
                return

            if conf.__name__ != ar_name:
                return

            num_switched = 0
            for ds in xml.findall('/ds/name'):
                if ds.text.strip() == ds1:
                    ds.text = ds2
                    num_switched += 1
                elif ds.text.strip() == ds2:
                    ds.text = ds1
                    num_switched += 1

            if num_switched != 2:
                print "ERROR: Unable to switch names for '{0}'!".format(
                    os.path.basename(archive))
                return

            print "INFO: Switching '{0}' and '{1}' in '{2}'.".format(
                ds1, ds2, os.path.basename(archive))
            changed = True
        else:
            print "ERROR: Invalid RRD convert action '%s'!" % action
            return

        if changed:
            try:
                os.rename(archive, "%s__bak" % archive)
                process = subprocess.Popen(
                    ['/usr/bin/rrdtool', 'restore', '-', archive],
                    stdin=subprocess.PIPE,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE)

                # Fix all empty last_ds values otherwise rrdtool will complain on restore
                for last_ds in xml.findall('/ds/last_ds'):
                    if not last_ds.text:
                        last_ds.text = "UNKN"

                process.communicate(ElementTree.tostring(xml.getroot()))
                if process.returncode != 0:
                    raise Exception

                try:
                    os.unlink("%s__bak" % archive)
                except:
                    pass
            except:
                os.rename("%s__bak" % archive, archive)
                raise
Beispiel #4
0
  def convert(conf, archive, action = "refresh", opts = "", graph = None):
    """
    Converts 
    """
    try:
      os.stat(archive)
    except OSError:
      return
    
    # Dump the archive into XML form
    process = subprocess.Popen(
      ['/usr/bin/rrdtool', 'dump', archive],
      stdout = subprocess.PIPE,
      stderr = subprocess.PIPE
    )
    
    # Make any transformations needed
    xml = ElementTree.parse(process.stdout)
    wanted_source_names = [source.name for source in conf.sources]
    start_offset = 5 # version, step, seconds comment, lastupdate, timestamp comment
    changed = False
    
    if action == "refresh":
      # A simple marker to rescan sources as something has been modified
      class RescanSources(Exception): pass
      
      while True:
        data_sources = xml.findall('/ds')
        data_source_names = [source.findtext('./name').strip() for source in data_sources]
        
        try:
          for ds in data_source_names:
            if ds not in wanted_source_names:
              print "WARNING: Removal of sources currently not supported!"
              # If we remove something we must continue the loop
              # raise RescanSources
          
          for idx, ds in enumerate(wanted_source_names):
            if ds not in data_source_names:
              print "INFO: Adding data source '%s' to RRD '%s.'" % (ds, os.path.basename(archive))
              
              # Update header
              dse = ElementTree.Element("ds")
              ElementTree.SubElement(dse, "name").text = conf.sources[idx].name
              ElementTree.SubElement(dse, "type").text = conf.sources[idx].type
              ElementTree.SubElement(dse, "minimal_heartbeat").text = str(conf.sources[idx].heartbeat) 
              ElementTree.SubElement(dse, "min").text = "NaN"
              ElementTree.SubElement(dse, "max").text = "NaN"
              ElementTree.SubElement(dse, "last_ds").text = "UNKN"
              ElementTree.SubElement(dse, "value").text = "0.0000000000e+00"
              ElementTree.SubElement(dse, "unknown_sec").text = "0"
              xml.getroot().insert(start_offset + idx, dse)
              
              # Update all RRAs
              for rra in xml.findall('/rra'):
                # Update RRA header
                cdp = rra.find('./cdp_prep')
                dse = ElementTree.Element("ds")
                ElementTree.SubElement(dse, "primary_value").text = "NaN"
                ElementTree.SubElement(dse, "secondary_value").text = "NaN"
                ElementTree.SubElement(dse, "value").text = "NaN"
                ElementTree.SubElement(dse, "unknown_datapoints").text = "0"
                cdp.insert(idx, dse)
                
                # Update all RRA datapoints
                for row in rra.findall('./database/row'):
                  v = ElementTree.Element("v")
                  v.text = "NaN"
                  row.insert(idx, v)
              
              changed = True
          
          break
        except RescanSources:
          pass
      
      # Add RRAs when they have changed (only addition is supported)
      for wanted_rra in conf.archives:
        for rra in xml.findall('/rra'):
          cf = rra.findtext('./cf').strip()
          steps = int(rra.findtext('./pdp_per_row').strip())
          rows = len(rra.findall('./database/row'))
          xff = float(rra.findtext('./params/xff').strip())
          
          match = all([
            cf == wanted_rra.cf,
            steps == wanted_rra.steps,
            rows == wanted_rra.rows,
            xff == wanted_rra.xff
          ])
          
          if match:
            break
        else:
          # Not found in existing RRAs, we need to add it
          print "INFO: Adding new RRA '%s' to '%s'." % (wanted_rra, os.path.basename(archive)) 
          changed =  True
          
          rra = ElementTree.SubElement(xml.getroot(), "rra")
          ElementTree.SubElement(rra, "cf").text = wanted_rra.cf
          ElementTree.SubElement(rra, "pdp_per_row").text = str(wanted_rra.steps)
          params = ElementTree.SubElement(rra, "params")
          ElementTree.SubElement(params, "xff").text = str(wanted_rra.xff)
          cdp_prep = ElementTree.SubElement(rra, "cdp_prep")
          
          for ds in conf.sources:
            dse = ElementTree.SubElement(cdp_prep, "ds")
            ElementTree.SubElement(dse, "primary_value").text = "NaN"
            ElementTree.SubElement(dse, "secondary_value").text = "NaN"
            ElementTree.SubElement(dse, "value").text = "NaN"
            ElementTree.SubElement(dse, "unknown_datapoints").text = "0"
          
          database = ElementTree.SubElement(rra, "database")
          for row in xrange(wanted_rra.rows):
            row = ElementTree.SubElement(database, "row")
            for v in xrange(len(conf.sources)):
              ElementTree.SubElement(row, "v").text = "NaN"
    elif action == "archive":
      # Archives data from RRDs
      print "INFO: Archiving RRD '%s.'" % os.path.basename(archive)
      
      for rra in xml.findall('/rra'):
        cf = rra.findtext('./cf').strip()
        if cf == 'AVERAGE':
          timestamp = None
          
          for item in rra.findall('./database')[0]:
            if item.tag == "row":
              # Data
              values = [x.text.strip() for x in item.findall('./v')]
              data = {}
              for i, x in enumerate(conf.sources):
                data[x.name] = values[i]
              
              if graph is not None:
                data_archive.record_data(graph, timestamp, data)
            else:
              # Comment
              timestamp = datetime.fromtimestamp(int(item.text.split("/")[1].strip()))
    elif action == "switch_sources":
      # Switches two data sources
      try:
        ar_name, ds1, ds2 = opts.split(',')
      except ValueError:
        print "ERROR: Invalid RRD update options for action switch_sources!"
        print "USAGE: <archive_class>,<ds1>,<ds2>"
        return
      
      if conf.__name__ != ar_name:
        return
      
      num_switched = 0
      for ds in xml.findall('/ds/name'):
        if ds.text.strip() == ds1:
          ds.text = ds2
          num_switched += 1
        elif ds.text.strip() == ds2:
          ds.text = ds1
          num_switched += 1

      if num_switched != 2:
        print "ERROR: Unable to switch names for '{0}'!".format(os.path.basename(archive))
        return
      
      print "INFO: Switching '{0}' and '{1}' in '{2}'.".format(ds1, ds2, os.path.basename(archive))
      changed = True
    else:
      print "ERROR: Invalid RRD convert action '%s'!" % action
      return
    
    if changed:
      try:
        os.rename(archive, "%s__bak" % archive)
        process = subprocess.Popen(
          ['/usr/bin/rrdtool', 'restore', '-', archive],
          stdin = subprocess.PIPE,
          stdout = subprocess.PIPE,
          stderr = subprocess.PIPE
        )
        
        # Fix all empty last_ds values otherwise rrdtool will complain on restore
        for last_ds in xml.findall('/ds/last_ds'):
          if not last_ds.text:
            last_ds.text = "UNKN"
        
        process.communicate(ElementTree.tostring(xml.getroot()))
        if process.returncode != 0:
          raise Exception
        
        try:
          os.unlink("%s__bak" % archive)
        except:
          pass
      except:
        os.rename("%s__bak" % archive, archive)
        raise