2020-06-09 16:02:25 +09:00
|
|
|
<?php
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Orrisroot\Rrd;
|
|
|
|
|
|
|
|
class Temperature
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var string database directory path
|
|
|
|
*/
|
|
|
|
private string $databasePath;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array sensor ids
|
|
|
|
*/
|
|
|
|
private array $sensorIds;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* constructor.
|
|
|
|
*
|
|
|
|
* @param string $fpath database directory path
|
|
|
|
*/
|
|
|
|
public function __construct(string $fpath)
|
|
|
|
{
|
|
|
|
$this->databasePath = $fpath;
|
|
|
|
$this->sensorIds = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* add sensor.
|
|
|
|
*
|
|
|
|
* @param string $id sensor id
|
|
|
|
*/
|
|
|
|
public function addSensor(string $id)
|
|
|
|
{
|
|
|
|
$this->sensorIds[] = $id;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* update database.
|
|
|
|
*
|
|
|
|
* @param string $id sensor id
|
|
|
|
* @param int $time timestamp
|
|
|
|
* @param float $value temperature value
|
|
|
|
*
|
|
|
|
* @return bool false if failure
|
|
|
|
*/
|
|
|
|
public function update(string $id, int $time, float $value): bool
|
|
|
|
{
|
|
|
|
$fpath = $this->getFilePath($id);
|
|
|
|
if (!file_exists($fpath)) {
|
2021-10-14 14:20:54 +09:00
|
|
|
if (!$this->create($id)) {
|
2020-06-09 16:02:25 +09:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$options = [sprintf('%u:%lf', $time, $value)];
|
|
|
|
|
|
|
|
return rrd_update($fpath, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* read last data.
|
|
|
|
*
|
|
|
|
* @param string $id sensor id
|
|
|
|
*
|
|
|
|
* @return ?array last data
|
|
|
|
*/
|
|
|
|
public function readLastData(string $id): ?array
|
|
|
|
{
|
|
|
|
static $options = [
|
|
|
|
'LAST',
|
|
|
|
];
|
|
|
|
$fpath = $this->getFilePath($id);
|
|
|
|
$res = rrd_fetch($fpath, $options);
|
|
|
|
if (false === $res) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$latest = 0;
|
|
|
|
$start = $res['start'];
|
|
|
|
$step = $res['step'];
|
|
|
|
$data = [];
|
|
|
|
foreach ($res['data'][$id] as $key => $datum) {
|
|
|
|
if (!is_nan($datum) && 0 != $datum) {
|
|
|
|
$data[] = $datum;
|
|
|
|
$latest = $start + ($key + 1) * $step;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (empty($data)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
$ret['id'] = $id;
|
|
|
|
$ret['min'] = min($data);
|
|
|
|
$ret['max'] = max($data);
|
|
|
|
$ret['average'] = array_sum($data) / count($data);
|
|
|
|
$ret['latest'] = $data[count($data) - 1];
|
|
|
|
$ret['timestamp'] = $latest;
|
|
|
|
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* create database.
|
|
|
|
*
|
|
|
|
* @param string $id sensor id
|
|
|
|
*
|
|
|
|
* @return bool false if failure
|
|
|
|
*/
|
2021-10-14 14:20:54 +09:00
|
|
|
private function create(string $id): bool
|
2020-06-09 16:02:25 +09:00
|
|
|
{
|
|
|
|
$fpath = $this->getFilePath($id);
|
|
|
|
$options = [
|
|
|
|
'--step', '60', // 1min step
|
2021-10-14 14:20:54 +09:00
|
|
|
sprintf('DS:%s:GAUGE:600:0:100', $id), // 10min hartbeat
|
2020-06-09 16:02:25 +09:00
|
|
|
'RRA:LAST:0.5:1:1440', // last/m - 1day(1440min)
|
|
|
|
'RRA:LAST:0.5:30:52560', // last/30m - 3years(365day=30min*17520)*3
|
|
|
|
];
|
|
|
|
|
|
|
|
return rrd_create($fpath, $options);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* get rrd file path.
|
|
|
|
*
|
|
|
|
* @param string $id sensor id
|
|
|
|
*
|
|
|
|
* @return string rrd file path
|
|
|
|
*/
|
|
|
|
private function getFilePath(string $id): string
|
|
|
|
{
|
|
|
|
return $this->databasePath.'/'.$id.'.rrd';
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* output graph.
|
|
|
|
*
|
|
|
|
* @param string $type output graph type
|
|
|
|
* @param string $fpath output image file path
|
|
|
|
*
|
|
|
|
* @return bool false if failure
|
|
|
|
*/
|
|
|
|
public function outputGraph(string $type, string $fpath): bool
|
|
|
|
{
|
|
|
|
static $colors = [
|
|
|
|
'#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF',
|
|
|
|
'#FF9999', '#99FF99', '#9999FF', '#FFFF99', '#99FFFF', '#FF99FF',
|
|
|
|
];
|
|
|
|
static $types = ['hour', 'day', 'week', 'month', 'year', '3year'];
|
|
|
|
if (!in_array($type, $types)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
$start = '3year' == $type ? '-3year' : '-1'.$type;
|
|
|
|
$options = [
|
|
|
|
'--imgformat', 'PNG',
|
|
|
|
'--lower-limit', '10',
|
|
|
|
'--upper-limit', '40',
|
|
|
|
'--start', $start,
|
|
|
|
'--end', 'now',
|
|
|
|
'--width', '400',
|
|
|
|
'--height', '200',
|
|
|
|
'--units-exponent', '0',
|
|
|
|
'--vertical-label', "Temperature [\xc2\xb0C]",
|
|
|
|
'--title', 'Server Room Temperature - by '.$type,
|
|
|
|
];
|
|
|
|
$idlen = 0;
|
|
|
|
foreach ($this->sensorIds as $key => $id) {
|
|
|
|
if (strlen($id) > $idlen) {
|
|
|
|
$idlen = strlen($id);
|
|
|
|
}
|
|
|
|
$options[] = sprintf('DEF:B%d=%s:%s:LAST', $key, $this->getFilePath($id), $id);
|
|
|
|
}
|
|
|
|
$options[] = sprintf('COMMENT: %s Cur\: Min\: Avg\: Max\:', str_repeat(' ', $idlen));
|
|
|
|
$options[] = sprintf('COMMENT:\l');
|
|
|
|
foreach ($this->sensorIds as $key => $id) {
|
|
|
|
$color = $colors[$key % count($colors)];
|
|
|
|
$options[] = sprintf('LINE2:B%d%s:%s%s', $key, $color, $id, str_repeat(' ', $idlen - strlen($id)));
|
|
|
|
$options[] = sprintf('GPRINT:B%d:LAST: %%-6.2lf', $key);
|
|
|
|
$options[] = sprintf('GPRINT:B%d:MIN: %%-6.2lf', $key);
|
|
|
|
$options[] = sprintf('GPRINT:B%d:AVERAGE: %%-6.2lf', $key);
|
|
|
|
$options[] = sprintf('GPRINT:B%d:MAX: %%-6.2lf\l', $key);
|
|
|
|
}
|
|
|
|
$options[] = sprintf('COMMENT:Last update\: %s\r', str_replace(':', '\:', date('Y-m-d H:i:s T')));
|
|
|
|
|
|
|
|
$res = rrd_graph($fpath, $options);
|
|
|
|
|
|
|
|
return false !== $res;
|
|
|
|
}
|
|
|
|
}
|