flm01/server/drupal/modules/logger/logger.rrd.inc

443 lines
12 KiB
PHP

<?php
/**
* Callbacks for accessing RRDs.
*
* Copyright (c) 2008-2009 jokamajo.org
* 2010-2011 Bart Van Der Meerssche <bart.vandermeerssche@flukso.net>
* 2010 Fraunhofer Institut ITWM (www.itwm.fraunhofer.de)
*
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
//Constants
//Some constants need to be redefined because this file can be called from xmlrpc.php
//drupal_get_path does not work, because xmlrpc.php is called from xmlrpc.php.
define('ROOT_PATH', 'sites/all/modules/logger');
define('RRDTOOL', ROOT_PATH . '/rrdtool');
define('DATA_PATH', ROOT_PATH . '/data');
define('SECOND', 1);
define('MINUTE', 60);
define('HOUR', 60 * MINUTE);
define('DAY', 24 * HOUR);
define('WEEK', 7 * DAY);
define('MONTH', 30 * DAY);
define('YEAR', 52 * WEEK);
define('METER', 'meter');
/**
* Creates an RRD for storing sensors' measurements in minute resolution.
*
* @param $meter The sensor's id.
* @return the creation command execution code.
*/
function logger_rrd_base_create($meter) {
$step = 1 * MINUTE;
$archives = logger_get_storage_periods();
return _logger_rrd_create($meter, $step, 'DERIVE', $archives, 'base');
}
/**
* Returns an array of measurement storage periods, indexed by time resolution.
*
* @return the array of storage periods.
*/
function logger_get_storage_periods() {
return array(
//resolution storage period
1 * MINUTE => 1 * DAY,
15 * MINUTE => 1 * WEEK,
1 * DAY => 365 * DAY,
1 * WEEK => 10 * YEAR);
}
/**
* Creates an RRD for storing sensors' night measurements in daily resolution.
*
* @param $meter The sensor's id.
* @return the creation command execution code.
*/
function logger_rrd_night_create($meter) {
$step = 24 * HOUR;
$archives = array(
//resolution storage period
1 * DAY => 365 * DAY,
1 * WEEK => 10 * YEAR);
return _logger_rrd_create($meter, $step, 'GAUGE', $archives, 'night');
}
/**
* Creates an RRD for storing sensors' measurements.
*
* @param $meter The sensor's id.
* @param $step The base interval in seconds with which data will be fed into the RRD.
* @param $ds_type The RRD DS type.
* @param $archives An array of RRD archives' properties formatted as (resolution => storage period).
* @param $subdir The subdirectory where the rrd file should be placed. (NULL means the default location)
* @return the creation command execution code.
*/
function _logger_rrd_create($meter, $step, $ds_type, $archives, $subdir) {
$return = 0;
$file_path = _logger_rrd_file($meter, $subdir);
$start = 1199487600; //Fri, 04 Jan 2008 23:00:00 GMT
$ds = _logger_rrd_meter_ds($ds_type);
if (!file_exists($file_path)) {
$command = RRDTOOL . " create $file_path " .
"--start $start " .
"--step $step " .
"$ds " .
_logger_rrd_rras($step, $archives);
system($command, $return);
}
return $return;
}
/**
* Creates a RRD DS definition.
*
* @param $type The DS type.
* @return the DS definition.
*/
function _logger_rrd_meter_ds($type) {
$name = METER;
$timeout = 100 * DAY;
$min = 0;
$max = 20;
return "DS:$name:$type:$timeout:$min:$max";
}
/**
* Creates a list of RRD archives' definitions.
*
* @param $step The base interval in seconds with which data will be fed into the RRD.
* @param $archives The array of RRD archives' properties formatted as (resolution => storage period).
* @return the list of RRD archives' definitions.
*/
function _logger_rrd_rras($step, $archives) {
$rras = '';
foreach($archives as $resolution => $storage_period) {
$rows = $resolution / $step;
$slots = $storage_period / $resolution;
$rras .= "RRA:AVERAGE:0.5:$rows:$slots ";
}
return $rras;
}
/**
* Feeds the sensors' RRDs with night consumption.
*
* @param $meter The sensor's id.
* @param $start The night period start time.
* @param $end The night period end time.
* @return the update command execution code.
*/
function logger_rrd_night_update($meter, $start, $end) {
$file_path = _logger_rrd_file($meter);
$resolution = 15 * MINUTE;
$command = RRDTOOL . " fetch $file_path " .
"AVERAGE " .
"--resolution $resolution " .
"--start $start " .
"--end $end " .
"| tail -n 12 | awk -F': ' '{SUM += $2} END {print SUM/12}'";
$value = (float) shell_exec($command);
return logger_rrd_update($meter, array($end => $value), 'night');
}
/**
* Feeds the sensors' RRDs with measurements.
*
* @param $meter The sensor's id.
* @param $values An array of measurements formatted as (timestamp => value).
* @param $subdir The subdirectory where the rrd file is placed.
* @return the update command execution code.
*/
function logger_rrd_update($meter, $values, $subdir = NULL) {
$return = 0;
$file_path = _logger_rrd_file($meter, $subdir);
$command = RRDTOOL . " update $file_path ";
ksort($values);
foreach($values as $timestamp => $value) {
$command .= " $timestamp:$value";
}
system($command, $return);
return $return;
}
/**
* Queries the RRDs for sensor's measurements.
*
* @param $interval The time interval. This argument is used to select the RRA.
* @param $sensor The sensor.
* @param $unit The power unit.
* @param $offset The user timezone offset.
* @param $period An array containing a period of time to be queried.
* @param $step The amount of time aggregated in a single point.
* @return the array of sensor's measurements.
*/
function logger_rrd_query_sensor($interval, $sensor, $unit, $offset, $period, $step) {
$series_id = METER;
$def = _logger_rrd_sensor_def($interval, $series_id, $sensor, $unit);
$latest = _logger_rrd_latest_timestamp($interval, $sensor);
return _logger_rrd_export($interval, $def, $series_id, $latest, $offset, true, $period, $step);
}
/**
* Returns the timestamp of the latest update of a sensor.
*
* @param $interval The time interval.
* @param $sensor The sensor.
* @return the latest update timestamp.
*/
function _logger_rrd_latest_timestamp($interval, $sensor) {
$file_path = _logger_rrd_file($sensor->meter, $interval);
$command = RRDTOOL . " last $file_path";
exec($command, $lines);
return $lines[0];
}
/**
* Queries the RRDs for sensors' aggregated measurements.
*
* @param $interval The time interval.
* @param $sensors The array of sensors.
* @param $unit The power unit.
* @param $offset The user timezone offset.
* @param $period An array containing a period of time to be queried.
* @param $step The amount of time aggregated in a single point.
* @return the array of sensor's measurements.
*/
function logger_rrd_query_agg($interval, $sensors, $unit, $offset, $period, $step) {
//Latest measurements are not considered, in order to tolerate heartbeat delays.
$end_time = time() - 15 * MINUTE;
$def = "";
$variables = "";
$i = 1;
foreach ($sensors as $sensor) {
$sensor_cdef = METER . $i;
$def .= _logger_rrd_sensor_def($interval, $sensor_cdef, $sensor, $unit) .
//Considers unknown measurements to be zero
"CDEF:completeseries$i=" . "$sensor_cdef,UN,0,$sensor_cdef,IF " .
//Do not consider latest minutes
"CDEF:filtered$i=TIME,$end_time,GT,UNKN,completeseries$i,IF ";
$variables .= ($i > 1 ? ',' : '') . "filtered$i";
$i++;
}
$operators = str_repeat(",+", count($sensors) - 1);
$series_id = METER . '0';
//Sum all measurements of a particular time
$def .= "CDEF:$series_id=$variables$operators ";
return _logger_rrd_export($interval, $def, $series_id, time(), $offset, false, $period, $step);
}
/**
* Queries the total energy consumption during the specified time period, in watt-hour.
*
* @param $sensors The sensors whose measurements are to be summed up.
* @param $unit The energy unit.
* @param $interval The time interval menu option.
* @param $offset The user timezone offset.
* @param $period An array containing a period of time to be queried.
* @param $step The amount of time aggregated in a single point.
* @return the summed power consumption.
*/
function logger_rrd_query_energy($sensors, $unit, $interval, $offset, $period, $step) {
$total = 0;
foreach ($sensors as $sensor) {
$measurements = logger_rrd_query_sensor($interval, $sensor, $unit, $offset, $period, $step);
foreach($measurements as $timestamp => $value) {
$total += $value * $step;
}
}
return $total;
}
/**
* Creates a DEF tag to represent sensor's measurements plotted in a chart series.
*
* @param $interval The time interval.
* @param $series_id The series id.
* @param $sensor The sensor.
* @param $unit The unit.
* @return the DEF tag.
*/
function _logger_rrd_sensor_def($interval, $series_id, $sensor, $unit) {
global $user;
$factor = _logger_rrd_get_factor($unit);
if($sensor->private > 0 && $sensor->uid != $user->uid) {
return null;
}
$file_path = _logger_rrd_file($sensor->meter, $interval);
return "DEF:data$series_id=" . "$file_path:meter:AVERAGE " .
"CDEF:$series_id=" . "data$series_id," . $factor . ",* ";
}
/**
* Composes RRD file path.
*
* @param $meter The sensor id.
* @param $interval The time interval.
* @return the RRD file path.
*/
function _logger_rrd_file($meter, $interval) {
$path = DATA_PATH . ($interval == 'night'? '/night' : '/base');
return "$path/$meter.rrd";
}
/**
* Exports the sensors' measurements from the RRDs to an array.
*
* @param $def The rrd def tag.
* @param $exported_cdef The exported cdef.
* @param $latest The latest update time.
* @param $offset The user timezone offset.
* @param $include_nan Whether NaN values should be included in the result.
* @param $period An array containing a period of time to be queried.
* @param $step The amount of time aggregated in a single point.
* @return the array of sensors' measurements.
*/
function _logger_rrd_export($interval, $def, $exported_cdef, $latest, $offset, $include_nan, $period, $step) {
if (!$def) {
return array();
}
$start = $period['start'];
$end = $period['end'];
//time period MUST be within the RRA and both $start and $end must be multiples of $step
$start = $start - ($start % $step);
$end = $end - ($end % $step);
$maxrows = 1 + ($end - $start) / $step;
$maxrows = $maxrows < 10? 10: $maxrows; //RRDTool exports at least 8 rows
$command = RRDTOOL . ' xport ' .
"--start $start " .
"--end $end " .
"--step $step " .
"--maxrows $maxrows " .
"$def " .
"XPORT:$exported_cdef";
exec($command, $lines);
return _logger_rrd_parse_exported_lines($lines, $offset);
}
/**
* Parses output of command rrdtool xport into an array.
*
* @param $lines The lines to be exported.
* @param $offset The user timezone offset.
* @return the array of sensors' measurements.
*/
function _logger_rrd_parse_exported_lines($lines, $offset) {
$data = array();
foreach($lines as $line) {
$line = strtolower($line);
if (strpos($line, '<row>') > 0) {
$line = str_replace(array('<row><t>', '</v></row>'), '', $line);
$line = str_replace('</t><v>', ':', $line);
$div_pos = strpos($line, ':');
$timestamp = trim(substr($line, 0, $div_pos)) + $offset;
$value = trim(substr($line, $div_pos + 1));
if ($value == 'nan') {
if ($include_nan) {
$data[$timestamp] = null;
}
} else {
$data[$timestamp] = $value;
}
}
}
return $data;
}
function _logger_rrd_get_factor($unit) {
$factors = array(
'kw' => 0.001,
'kwh' => 31536,
'eur' => 5676, // 18 EURcent/kWh
'aud' => 5991, // 19 AUDcent/kWh
'watt' => 3600, // 1Wh/s = 3600 W
'Wh' => 1,
'kWh' => 0.001
);
return $factors[$unit];
}