#!/usr/bin/env php
<?php
/* created by joe z (joseph@nyi.net) */
if (count($argv) !== 5) {
  echo "\nUsage: " . basename($argv[0]) . " oldRRD1 oldRRD2 timestamp newRRD\n\n";
  echo "This script merges two given observium RRD files, <oldRRD1> and <oldRRD2>,\n";
  echo "with the data being merged around <timestamp>, and outputs <newRRD>\n\n";
  exit;
}

function get_header($rrd) {
  $return = array();
  foreach($rrd as $key => &$value) {
    if (trim($value) === '<rrd>') {
      return $return;
    }
    $return[$key] = trim($value);
  }
  return FALSE;
}

function get_subheader($rrd) {
  $return = array();
  $start = FALSE;
  foreach($rrd as $key => $value) {
    if (!$start && trim($value) !== '<rrd>') {
      continue;
    }
    $start = TRUE;
    if (trim($value) === '' || trim($value) === '<rrd>') {
      continue;
    }
    if (trim($value) === '<ds>') {
      return $return;
    }
    $return[$key] = trim($value);
  }
  return FALSE;
}

function get_ds($rrd) {
  $return = array();
  $i = 0;
  $capture = FALSE;
  foreach($rrd as $key => &$value) {
    $value = trim($value);
    if ($value === '<ds>') {
      $capture = TRUE;
    }
    if ($value === '</ds>') {
      $i++;
      $capture = FALSE;
    }
    if ($value === '<rra>') {
      return $return;
    }
    if ($capture && $value !== '<ds>') {
      $property = array();
      $ret = preg_match("!<([^/].+?)>(.*?)</.+?>!", $value, $property);
      if ($ret === 0) {
        continue;
      }
      $return[$i][$property[1]] = $property[2];
    }
  }
  return FALSE;
}

function get_rra($rrd) {
  $return = array();
  $rra_i = 0;
  $ds_i = 0;
  $database_i = 0;
  $capture = FALSE;
  $params = FALSE;
  $cdp_prep = FALSE;
  $ds = FALSE;
  $database = FALSE;
  foreach($rrd as $key => &$value) {
    $value = trim($value);
    if ($value === '<rra>') {
      $capture = TRUE;
    }
    if ($value === '</rra>') {
      $rra_i++;
      $database_i = 0;
      $capture = FALSE;
    }
    if ($value === '</rrd>') {
      return $return;
    }
    if ($capture && $value !== '<rra>') {
      $property = array();
      $ret = preg_match("!<([^/].+?)>(.*?)</.+?>!", $value, $property);
      if ($ret === 0) {
        $ret = preg_match("!<(/?params|/?cdp_prep|/?ds|/?database)>!", $value, $property);
        if ($ret === 0) {
           continue;
        }
        if ($value === '<params>') {
          $params = TRUE;
          continue;
        }
        if ($value === '<cdp_prep>') {
          $cdp_prep = TRUE;
          continue;
        }
        if ($value === '<ds>') {
          $ds = TRUE;
          continue;
        }
        if ($value === '<database>') {
          $database = TRUE;
          continue;
        }
        if ($value === '</params>') {
          $params = FALSE;
          continue;
        }
        if ($value === '</cdp_prep>') {
          $cdp_prep = FALSE;
          $ds_i = 0;
          continue;
        }
        if ($value === '</ds>') {
          $ds = FALSE;
          $ds_i++;
          continue;
        }
        if ($value === '</database>') {
          $database = FALSE;
          continue;
        }
      }
      if (!$params && !$cdp_prep && !$ds && !$database) {
        $return[$rra_i][$property[1]] = $property[2];
      }
      else {
        if ($params) {
          $return[$rra_i]['params'][$property[1]] = $property[2];
        }
        if ($cdp_prep && $ds) {
            $return[$rra_i]['cdp_prep']['ds'][$ds_i][$property[1]] = $property[2];
        }
        if ($database) {
          $ret = preg_match("%(<!--.+?-->)%", $value, $timestamp);
          if ($ret === 0) {
            echo "Error: no timestamp found for RRD database row.\n";
            print_r($value);
            exit;
          }
          $return[$rra_i]['database'][$database_i]['timestamp'] = $timestamp[1];
          $entries = explode("</v><v>", $value);
          if ($entries[0] === $value) {
            echo "Error: no values found for RRD database row.\n";
            print_r($value);
            exit;
          }
          foreach ($entries as $entryNum => &$entry) {
            if ($entryNum === 0) {
              $n = strpos($entry, "<v>");
              if ($n === FALSE) {
                echo "Error: invalid values found for RRD database row.\n";
                print_r($value);
                exit;
              }
              $entry = substr($entry, $n + 3);
            }
            elseif ($entryNum === count($entries) - 1) {
              $n = strpos($entry, "</v>");
              if ($n === FALSE) {
                echo "Error: invalid values found for RRD database row.\n";
                print_r($value);
                exit;
              }
              $entry = substr($entry, 0, $n);
            }
            else {
              continue;
            }
          }
          $return[$rra_i]['database'][$database_i]['v'] = $entries;
          $database_i++;
        }
      }
    }
  }
  return FALSE;
}

function array_diff_recursive($aArray1, $aArray2) {
  $aReturn = array();
  foreach ($aArray1 as $mKey => $mValue) {
    if (array_key_exists($mKey, $aArray2)) {
      if (is_array($mValue)) {
        $aRecursiveDiff = array_diff_recursive($mValue, $aArray2[$mKey]);
        if (count($aRecursiveDiff)) { $aReturn[$mKey] = $aRecursiveDiff; }
      } else {
        if ($mValue != $aArray2[$mKey]) {
          $aReturn[$mKey] = $mValue;
        }
      }
    } else {
      $aReturn[$mKey] = $mValue;
    }
  }
  return $aReturn;
} 

$oldRRD1 = array();
$return = 0;
exec("rrdtool dump " . $argv[1], $oldRRD1, $return);
if ($return !== 0) {
  exit;
}

$oldRRD2 = array();
exec("rrdtool dump " . $argv[2], $oldRRD2, $return);
if ($return !== 0) {
  exit;
}

if (!is_numeric($argv[3])) {
  echo "Error: invalid timestamp " . $argv[3] . "\n";
  exit;
}
$mergeTime = $argv[3];

$oldHeader1 = get_header($oldRRD1);
$oldHeader2 = get_header($oldRRD2);
if ($oldHeader1 !== $oldHeader2) {
  echo "Error: RRD file headers don't match:\n";
  echo "Header 1:\n" . print_r($oldHeader1, TRUE);
  echo "Header 2:\n" . print_r($oldHeader2, TRUE);
  exit;
}
$oldSubHeader1 = get_subheader($oldRRD1);
$oldSubHeader2 = get_subheader($oldRRD2);
$oldDS1 = get_ds($oldRRD1);
$oldDS2 = get_ds($oldRRD2);
if(!is_array($oldDS1) || !is_array($oldDS2)) {
var_dump($oldDS1);
var_dump($oldDS2);
exit;
}
$diffDS = array_diff_recursive($oldDS1, $oldDS2);
if (!empty($diffDS)) {
  echo "Error: RRD DS structures don't match:\n";
  echo "DS 1:\n" . print_r($oldDS1, TRUE);
  echo "DS 2:\n" . print_r($oldDS2, TRUE);
  echo "Difference:\n" . print_r(array_diff_recursive($oldDS1, $oldDS2), TRUE);
  exit;
}
$oldRRA1 = get_rra($oldRRD1);
$oldRRA2 = get_rra($oldRRD2);

$newRRD = array();
$newRRD['header'] = $oldHeader2;
$newRRD['subheader'] = $oldSubHeader2;
$newRRD['ds'] = $oldDS2;

foreach ($oldRRA1 as $rraNum => $rra) {
  foreach ($rra['database'] as $databaseNum => &$database) {
    $rowTime = explode(" / ", $database['timestamp']);
    $time = rtrim($rowTime[1], " -->");
    if ($time < $mergeTime) {
      $databaseArray = $database;
      //$databaseArray['RRDfile'] = 'older';
      $newRRD['rra'][$rraNum]['database'][] = $databaseArray;
    }
  }
}

foreach ($oldRRA2 as $rraNum => $rra) {
  foreach ($rra as $key => $value) {
    if ($key !== 'database') {
      $newRRD['rra'][$rraNum][$key] = $value;
    }
  }
  foreach ($rra['database'] as $databaseNum => &$database) {
    $rowTime = explode(" / ", $database['timestamp']);
    $time = rtrim($rowTime[1], " -->");
    if ($time >= $mergeTime) {
      $databaseArray = $database;
      //$databaseArray['RRDfile'] = 'newer';
      $newRRD['rra'][$rraNum]['database'][] = $databaseArray;
    }
  }
}

foreach ($newRRD['rra'] as $rraNum => &$rra) {
  $rra = array_merge(array_flip(array('cf', 'pdp_per_row', 'params', 'cdp_prep', 'database')), $rra);
}


$newXML = '';
foreach ($newRRD['header'] as $line) {
  $newXML .= $line . "\n";
}
$newXML .= "<rrd>" . "\n";
foreach ($newRRD['subheader'] as $line) {
  $newXML .= "\t" . $line . "\n";
}
foreach ($newRRD['ds'] as $dsNum => $ds) {
  $newXML .= "\t" . "<ds>" . "\n";
  foreach ($ds as $key => $value) {
    if ($key === 'last_ds') {
      $newXML .= "\t\t" . "<!-- PDP Status -->" . "\n";
    }
    $newXML .= "\t\t" . "<" . $key . ">" . $value . "</" . $key . ">" . "\n";
  }
  $newXML .= "\t" . "</ds>" . "\n";  
}
$newXML .= "\t" . "<!-- Round Robin Archives -->" . "\n";
foreach ($newRRD['rra'] as $rraNum => $rra) {
  $newXML .= "\t" . "<rra>" . "\n";
  foreach ($rra as $key => $value) {
    if ($key === 'params') {
      $newXML .= "\t\t" . "<params>" . "\n";
      foreach ($value as $key1 => $value1) {
        $newXML .= "\t\t" . "<" . $key1 . ">" . $value1 . "</" . $key1 . ">" . "\n";
      }
      $newXML .= "\t\t" . "</params>" . "\n";
    }
    elseif ($key === 'cdp_prep') {
      $newXML .= "\t\t" . "<cdp_prep>" . "\n";
      foreach ($value as $dsArray) {
        foreach ($dsArray as $dsNum => $ds) {
        $newXML .= "\t\t\t" . "<ds>" . "\n";
          foreach ($ds as $key1 => $value1) {
            $newXML .= "\t\t\t" . "<" . $key1 . ">" . $value1 . "</" . $key1 . ">" . "\n";
          }
        $newXML .= "\t\t\t" . "</ds>" . "\n";
        }
      }
      $newXML .= "\t\t" . "</cdp_prep>" . "\n";
    }
    elseif ($key === 'database') {
      $newXML .= "\t\t" . "<database>" . "\n";
      foreach ($value as $databaseNum => $database) {
        $newXML .= "\t\t\t" . $database['timestamp'] . " ";
        $newXML .= "<row>";
        foreach ($database['v'] as $key1 => $value1) {
          $newXML .= "<v>" . $value1 . "</v>";
        }
        $newXML .= "</row>" . "\n";      
      }
      $newXML .= "\t\t" . "</database>" . "\n";
    }
    else {
      $newXML .= "\t\t" . "<" . $key . ">" . $value . "</" . $key . ">" . "\n";
    }
  }
  $newXML .= "\t" . "</rra>" . "\n";
}
$newXML .= "</rrd>" . "\n";

ini_set('memory_limit', '1024M');
file_put_contents($argv[4] . ".xml", $newXML);

$return = 0;
exec("rrdtool restore " . $argv[4] . ".xml " . $argv[4] . ".rrd -r", $output, $return);
if ($return !== 0 || !empty($output)) {
  echo "\nError creating RRD from merged XML dumps.\n";
  echo "Command output: " . print_r($output) . "\n";
  exit;
}

echo "Done.\n";
exit;
?>
