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 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