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)) { if (!$this->create($id)) { 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 */ private function create(string $id): bool { $fpath = $this->getFilePath($id); $options = [ '--step', '60', // 1min step sprintf('DS:%s:GAUGE:600:0:100', $id), // 10min hartbeat 'RRA:LAST:0.5:1:10080', // last/m - 7day(10080min) 'RRA:LAST:0.5:30:175200', // last/30m - 10years(365day=30min*17520)*10 ]; 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; } }