. * * @link https://www.librenms.org * * @copyright 2020 Tony Murray * @copyright 2014 Neil Lathwood * @author Tony Murray */ namespace LibreNMS\Data\Store; use InfluxDB\Client; use InfluxDB\Driver\UDP; use LibreNMS\Config; use LibreNMS\Data\Measure\Measurement; use Log; class InfluxDB extends BaseDatastore { /** @var \InfluxDB\Database */ private $connection; public function __construct(\InfluxDB\Database $influx) { parent::__construct(); $this->connection = $influx; // if the database doesn't exist, create it. try { if (! $influx->exists()) { $influx->create(); } } catch (\Exception $e) { Log::warning('InfluxDB: Could not create database'); } } public function getName() { return 'InfluxDB'; } public static function isEnabled() { return Config::get('influxdb.enable', false); } /** * Datastore-independent function which should be used for all polled metrics. * * RRD Tags: * rrd_def RrdDefinition * rrd_name array|string: the rrd filename, will be processed with rrd_name() * rrd_oldname array|string: old rrd filename to rename, will be processed with rrd_name() * rrd_step int: rrd step, defaults to 300 * * @param array $device * @param string $measurement Name of this measurement * @param array $tags tags for the data (or to control rrdtool) * @param array|mixed $fields The data to update in an associative array, the order must be consistent with rrd_def, * single values are allowed and will be paired with $measurement */ public function put($device, $measurement, $tags, $fields) { $stat = Measurement::start('write'); $tmp_fields = []; $tmp_tags['hostname'] = $device['hostname']; foreach ($tags as $k => $v) { if (empty($v)) { $v = '_blank_'; } $tmp_tags[$k] = $v; } foreach ($fields as $k => $v) { if ($k == 'time') { $k = 'rtime'; } if (($value = $this->forceType($v)) !== null) { $tmp_fields[$k] = $value; } } if (empty($tmp_fields)) { Log::warning('All fields empty, skipping update', ['orig_fields' => $fields]); return; } Log::debug('InfluxDB data: ', [ 'measurement' => $measurement, 'tags' => $tmp_tags, 'fields' => $tmp_fields, ]); try { $points = [ new \InfluxDB\Point( $measurement, null, // the measurement value $tmp_tags, $tmp_fields // optional additional fields ), ]; $this->connection->writePoints($points); $this->recordStatistic($stat->end()); } catch (\InfluxDB\Exception $e) { Log::error('InfluxDB exception: ' . $e->getMessage()); Log::debug($e->getTraceAsString()); } } /** * Create a new client and select the database * * @return \InfluxDB\Database */ public static function createFromConfig() { $host = Config::get('influxdb.host', 'localhost'); $transport = Config::get('influxdb.transport', 'http'); $port = Config::get('influxdb.port', 8086); $db = Config::get('influxdb.db', 'librenms'); $username = Config::get('influxdb.username', ''); $password = Config::get('influxdb.password', ''); $timeout = Config::get('influxdb.timeout', 0); $verify_ssl = Config::get('influxdb.verifySSL', false); $client = new Client($host, $port, $username, $password, $transport == 'https', $verify_ssl, $timeout, $timeout); if ($transport == 'udp') { $client->setDriver(new UDP($host, $port)); } return $client->selectDB($db); } private function forceType($data) { /* * It is not trivial to detect if something is a float or an integer, and * therefore may cause breakages on inserts. * Just setting every number to a float gets around this, but may introduce * inefficiencies. */ if (is_numeric($data)) { return floatval($data); } return $data === 'U' ? null : $data; } /** * Checks if the datastore wants rrdtags to be sent when issuing put() * * @return bool */ public function wantsRrdTags() { return false; } }